From cd16143084158c2c7b55825cf4e893e7b9f83ba3 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 14 Feb 2017 18:02:46 -0800 Subject: [PATCH 001/165] [Gradle Release Plugin] - pre tag commit: 'v0.6.0'. --- CHANGELOG.md | 14 ++- README.md | 8 +- build.gradle | 2 +- core-android/build.gradle | 12 +-- .../android/net/http/AndroidHttpClient.java | 32 ------ .../core/auth/AuthenticationError.java | 5 + .../sdk/android/core/auth/LoginActivity.java | 2 +- .../sdk/android/core/auth/LoginManager.java | 9 +- .../sdk/android/core/auth/SsoDeeplink.java | 46 ++++---- .../sdk/android/core/utils/AppProtocol.java | 35 ++++-- .../uber/sdk/android/core/UberButtonTest.java | 102 +++++++----------- .../android/core/auth/LoginButtonTest.java | 37 +++---- .../android/core/auth/LoginManagerTest.java | 19 ++-- .../core/auth/OAuthWebViewClientTest.java | 18 ---- .../android/core/auth/SsoDeeplinkTest.java | 48 ++------- .../android/core/utils/AppProtocolTest.java | 15 +-- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- rides-android/build.gradle | 4 +- .../sdk/android/rides/RequestDeeplink.java | 9 +- .../android/rides/RideRequestActivity.java | 2 +- .../sdk/android/rides/RideRequestButton.java | 2 +- .../sdk/android/rides/RideRequestView.java | 4 +- .../android/net/http/AndroidHttpClient.java | 32 ------ .../android/rides/RequestDeeplinkTest.java | 5 +- .../rides/RideRequestActivityTest.java | 4 +- .../android/rides/RideRequestViewTest.java | 24 +---- .../android/rides/RobolectricTestBase.java | 4 +- .../RideRequestButtonControllerTest.java | 6 +- .../resources/__files/prices_estimate.json | 32 +++--- samples/login-sample/build.gradle | 2 +- samples/request-button-sample/build.gradle | 2 +- .../android/rides/samples/SampleActivity.java | 4 - .../sdk/android/core/RobolectricTestBase.java | 4 +- .../uber/sdk/android/core/SdkPreferences.java | 2 +- 35 files changed, 209 insertions(+), 343 deletions(-) delete mode 100644 core-android/src/main/java/android/net/http/AndroidHttpClient.java delete mode 100644 rides-android/src/test/java/android/net/http/AndroidHttpClient.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b67d39e..217b523e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ -v0.5.5 - TBD ------------- +v0.6.0 - 2/14/2017 +------------------- +### Fixed +- [Issue #71](https://github.com/uber/rides-android-sdk/issues/71) LoginManager breaks when used with Rides SDK 0.6.0 +- [Issue #65](https://github.com/uber/rides-android-sdk/issues/65) Empty AndroidHttpClient get compiled when using core-android 0.5.4 leading to unresolved references to actual methods of AndroidHttpClient + +### Added +- Updated to match API 1.2 changes + +### Breaking +- Removed Region (China) support v0.5.4 - 9/29/2016 ------------------ @@ -13,6 +22,7 @@ v0.5.4 - 9/29/2016 - [Issue #48](https://github.com/uber/rides-android-sdk/issues/48) Typo in Readme.md - [Issue #54](https://github.com/uber/rides-android-sdk/issues/54) log4j lib still exists in 0.5.3 causing Proguard failing + v0.5.3 - 8/12/2016 ------------------ ### Fixed diff --git a/README.md b/README.md index c71d2da3..1122e83e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ To use the Uber Rides Android SDK, add the compile dependency with the latest ve Add the Uber Rides Android SDK to your `build.gradle`: ```gradle dependencies { - compile 'com.uber.sdk:rides-android:0.5.4' + compile 'com.uber.sdk:rides-android:0.6.0' } ``` @@ -31,7 +31,7 @@ In the `pom.xml` file: com.uber.sdk rides-android - 0.5.4 + 0.6.0 ``` @@ -319,7 +319,7 @@ service.getUserProfile().enqueue(new Callback() { ## Sample Apps -Sample apps 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.5.4). +Sample apps 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.6.0). The Sample apps require configuration parameters to interact with the Uber API, these include the client id, redirect uri, and server token. They are provided on the [Uber developer dashboard](https://developer.uber.com/dashboard). @@ -349,6 +349,4 @@ As the Uber Android SDK get closer to a 1.0 release, the API's will become more We :heart: contributions. Found a bug or looking for a new feature? Open an issue and we'll respond as fast as we can. Or, better yet, implement it yourself and open a pull request! We ask that you include tests to show the bug was fixed or the feature works as expected. -**Note:** All contributors also need to fill out the [Uber Contributor License Agreement](http://t.uber.com/cla) before we can merge in any of your changes. - ## MIT Licensed diff --git a/build.gradle b/build.gradle index 10c8b8de..b19e2fcd 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.3' classpath 'net.researchgate:gradle-release:2.3.5' classpath 'co.riiid:gradle-github-plugin:0.4.2' classpath 'net.saliman:gradle-cobertura-plugin:2.3.1' diff --git a/core-android/build.gradle b/core-android/build.gradle index 34179451..2f2d8935 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -10,7 +10,7 @@ buildscript { } dependencies { - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' } } @@ -129,12 +129,6 @@ uploadArchives { name 'Ty Smith' email 'tys@uber.com' } - - developer { - id 'yhartanto' - name 'Yohan Hartanto' - email 'yohan@uber.com' - } } } } @@ -153,7 +147,7 @@ cobertura { } dependencies { - compile ('com.uber.sdk:rides:0.5.2') { + compile ('com.uber.sdk:rides:0.6.0') { //Do not need Google OAuth based logic in Android exclude group: 'org.slf4j', module: 'slf4j-log4j12' exclude group: 'com.google.oauth-client', module: 'google-oauth-client' @@ -171,7 +165,7 @@ dependencies { testCompile 'com.google.http-client:google-http-client-jackson2:1.19.0' testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'org.assertj:assertj-core:1.7.1' - testCompile 'org.robolectric:robolectric:3.0' + testCompile 'org.robolectric:robolectric:3.2.2' //Needed for Cobertura checks because of above exclude testCompile 'com.google.http-client:google-http-client-jackson2:1.19.0' diff --git a/core-android/src/main/java/android/net/http/AndroidHttpClient.java b/core-android/src/main/java/android/net/http/AndroidHttpClient.java deleted file mode 100644 index 4d44478a..00000000 --- a/core-android/src/main/java/android/net/http/AndroidHttpClient.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2016 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 android.net.http; - -/** - * Apache HTTP Client was deprecated and is no longer part of compile classpath. - * Robolectic still links to it so an empty class is needed for compiling purposes. - * - * See Robolectric issue for - * details. - */ -public class AndroidHttpClient { } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java index 2b27f7b0..e7a68a31 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java @@ -31,6 +31,11 @@ */ public enum AuthenticationError { + /** + * User cancelled flow + */ + CANCELLED, + /** * There was a connectivity error while trying to load. */ diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 2f75ad01..3b78bfd9 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -177,7 +177,7 @@ String buildUrl( Uri.Builder builder = new Uri.Builder(); builder.scheme(HTTPS) - .authority(ENDPOINT + "." + configuration.getEndpointRegion().domain) + .authority(ENDPOINT + "." + configuration.getEndpointRegion().getDomain()) .appendEncodedPath(PATH) .appendQueryParameter(CLIENT_ID_PARAM, configuration.getClientId()) .appendQueryParameter(REDIRECT_PARAM, redirectUri) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index abfa9b23..cfa61285 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -84,7 +84,7 @@ public class LoginManager { static final int REQUEST_CODE_LOGIN_DEFAULT = 1001; - private static final String USER_AGENT = "core-android-v0.5.4-login_manager"; + private static final String USER_AGENT = "core-android-v0.6.0-login_manager"; private final AccessTokenManager accessTokenManager; private final LoginCallback callback; @@ -144,7 +144,6 @@ public void login(@NonNull Activity activity) { SsoDeeplink ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(sessionConfiguration.getClientId()) - .region(sessionConfiguration.getEndpointRegion()) .scopes(sessionConfiguration.getScopes()) .customScopes(sessionConfiguration.getCustomScopes()) .activityRequestCode(requestCode) @@ -304,7 +303,11 @@ private void handleResultCancelled( final AuthenticationError authenticationError = (error != null) ? AuthenticationError.fromString(error) : AuthenticationError.UNKNOWN; - if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && + if (authenticationError.equals(AuthenticationError.CANCELLED)) { + // User canceled login + callback.onLoginCancel(); + return; + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { loginForImplicitGrant(activity); return; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 0ebd3894..a1cc8975 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -35,14 +35,12 @@ import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.android.core.utils.PackageManagers; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import static com.uber.sdk.android.core.UberSdk.UBER_SDK_LOG_TAG; -import static com.uber.sdk.android.core.utils.AppProtocol.UBER_PACKAGE_NAME; import static com.uber.sdk.android.core.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; import static com.uber.sdk.android.core.utils.Preconditions.checkState; @@ -56,11 +54,10 @@ public class SsoDeeplink implements Deeplink { public static final int DEFAULT_REQUEST_CODE = LoginManager.REQUEST_CODE_LOGIN_DEFAULT; @VisibleForTesting - static final int MIN_VERSION_SUPPORTED = 31256; + static final int MIN_VERSION_SUPPORTED = 31302; private static final String URI_QUERY_CLIENT_ID = "client_id"; private static final String URI_QUERY_SCOPE = "scope"; - private static final String URI_QUERY_LOGIN_TYPE = "login_type"; private static final String URI_QUERY_PLATFORM = "sdk"; private static final String URI_QUERY_SDK_VERSION = "sdk_version"; private static final String URI_HOST = "connect"; @@ -69,7 +66,6 @@ public class SsoDeeplink implements Deeplink { private final String clientId; private final Collection requestedScopes; private final Collection requestedCustomScopes; - private final SessionConfiguration.EndpointRegion region; private final int requestCode; AppProtocol appProtocol; @@ -77,11 +73,9 @@ public class SsoDeeplink implements Deeplink { SsoDeeplink( @NonNull Activity activity, @NonNull String clientId, - @NonNull SessionConfiguration.EndpointRegion region, @NonNull Collection requestedScopes, @NonNull Collection requestedCustomScopes, int requestCode) { - this.region = region; this.activity = activity; this.clientId = clientId; this.requestCode = requestCode; @@ -101,10 +95,17 @@ public void execute() { checkState(isSupported(), "Single sign on is not supported on the device. " + "Please install or update to the latest version of Uber app."); - final Uri deepLinkUri = createSsoUri(); + Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setPackage(AppProtocol.UBER_PACKAGE_NAME); + final Uri deepLinkUri = createSsoUri(); intent.setData(deepLinkUri); + + for (String installedPackage : AppProtocol.UBER_PACKAGE_NAMES) { + if (PackageManagers.isPackageAvailable(activity, installedPackage)) { + intent.setPackage(installedPackage); + break; + } + } activity.startActivityForResult(intent, requestCode); } @@ -118,7 +119,6 @@ private Uri createSsoUri() { .authority(URI_HOST) .appendQueryParameter(URI_QUERY_CLIENT_ID, clientId) .appendQueryParameter(URI_QUERY_SCOPE, scopes) - .appendQueryParameter(URI_QUERY_LOGIN_TYPE, region.name()) .appendQueryParameter(URI_QUERY_PLATFORM, AppProtocol.PLATFORM) .appendQueryParameter(URI_QUERY_SDK_VERSION, BuildConfig.VERSION_NAME) .build(); @@ -131,10 +131,18 @@ private Uri createSsoUri() { */ @Override public boolean isSupported() { - final PackageInfo packageInfo = PackageManagers.getPackageInfo(activity, UBER_PACKAGE_NAME); + + PackageInfo packageInfo = null; + for (String installedPackage : AppProtocol.UBER_PACKAGE_NAMES) { + if (PackageManagers.isPackageAvailable(activity, installedPackage)) { + packageInfo = PackageManagers.getPackageInfo(activity, installedPackage); + break; + } + } + return (packageInfo != null) - && (packageInfo.versionCode >= MIN_VERSION_SUPPORTED) && - appProtocol.validateSignature(activity, UBER_PACKAGE_NAME); + && appProtocol.validateMinimumVersion(activity, packageInfo, MIN_VERSION_SUPPORTED) + && appProtocol.validateSignature(activity, packageInfo.packageName); } public static class Builder { @@ -144,7 +152,6 @@ public static class Builder { private String clientId; private Collection requestedScopes; private Collection requestedCustomScopes; - private SessionConfiguration.EndpointRegion region; private int requestCode = DEFAULT_REQUEST_CODE; public Builder(@NonNull Activity activity) { @@ -171,11 +178,6 @@ public Builder customScopes(@NonNull Collection customScopes) { return this; } - public Builder region(@NonNull SessionConfiguration.EndpointRegion region) { - this.region = region; - return this; - } - public Builder activityRequestCode(int requestCode) { this.requestCode = requestCode; return this; @@ -186,10 +188,6 @@ public SsoDeeplink build() { checkNotEmpty(requestedScopes, "Scopes must be set."); - if (region == null) { - region = SessionConfiguration.EndpointRegion.WORLD; - } - if (requestedCustomScopes == null) { requestedCustomScopes = new ArrayList<>(); } @@ -197,7 +195,7 @@ public SsoDeeplink build() { if (requestCode == DEFAULT_REQUEST_CODE) { Log.i(UBER_SDK_LOG_TAG, "Request code is not set, using default request code"); } - return new SsoDeeplink(activity, clientId, region, requestedScopes, requestedCustomScopes, requestCode); + return new SsoDeeplink(activity, clientId, requestedScopes, requestedCustomScopes, requestCode); } } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 0359d94b..61f8c1e4 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -17,7 +17,8 @@ import javax.annotation.Nullable; public class AppProtocol { - public static final String UBER_PACKAGE_NAME = "com.ubercab"; + public static final String[] UBER_PACKAGE_NAMES = + {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo"}; public static final String DEEPLINK_SCHEME = "uber"; public static final String PLATFORM = "android"; @@ -31,13 +32,24 @@ private static HashSet buildAppSignatureHashes() { set.add(UBER_RIDER_HASH); return set; } + + /** + * Validates minimum version of app required or returns true if in debug. + */ + public boolean validateMinimumVersion(Context context, PackageInfo packageInfo, int minimumVersion) { + if (isDebug(context)) { + return true; + } + + return packageInfo.versionCode >= minimumVersion; + } + + /** + * Validates the app signature required or returns true if in debug. + */ @SuppressLint("PackageManagerGetSignatures") public boolean validateSignature(Context context, String packageName) { - String brand = Build.BRAND; - int applicationFlags = context.getApplicationInfo().flags; - if ((brand.startsWith("Android") || brand.startsWith("generic")) && - (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - // We are debugging on an emulator, don't validate package signature. + if (isDebug(context)) { return true; } @@ -96,4 +108,15 @@ public String getAppSignature(@NonNull Context context) { MessageDigest getSha1MessageDigest() throws NoSuchAlgorithmException { return MessageDigest.getInstance(HASH_ALGORITHM_SHA1); } + + private boolean isDebug(Context context) { + String brand = Build.BRAND; + int applicationFlags = context.getApplicationInfo().flags; + if ((brand.startsWith("Android") || brand.startsWith("generic")) && + (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // We are debugging on an emulator, don't validate package signature. + return true; + } + return false; + } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/UberButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/UberButtonTest.java index 42c1588d..7ba06cfc 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/UberButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/UberButtonTest.java @@ -37,11 +37,6 @@ import org.junit.Test; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; -import org.robolectric.res.Attribute; -import org.robolectric.shadows.CoreShadowsAdapter; -import org.robolectric.shadows.RoboAttributeSet; - -import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -54,23 +49,6 @@ */ public class UberButtonTest extends RobolectricTestBase { - 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"; @@ -83,7 +61,6 @@ public class UberButtonTest extends RobolectricTestBase { private static final String TEXT = "test"; private static final String UBER_PACKAGE_NAME = "com.uber.sdk.android.core"; - private static final String UBER_ATTR_UBER_STYE = UBER_PACKAGE_NAME + ":attr/ub__style"; private Context context; @@ -94,22 +71,22 @@ public void setup() { @Test public void onCreate_whenBackgroundAttributeSet_shouldSetBackground() { - AttributeSet attributeSet = makeAttributeSet( - makeAttribute(ANDROID_ATTR_BACKGROUND, ANDROID_COLOR_WHITE) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.background, ANDROID_COLOR_WHITE) + .build(); UberButton uberButton = new UberButton(context, 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) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.drawableLeft, DRAWABLE_UBER_BADGE) + .addAttribute(android.R.attr.drawableTop, DRAWABLE_UBER_BADGE) + .addAttribute(android.R.attr.drawableRight, DRAWABLE_UBER_BADGE) + .addAttribute(android.R.attr.drawableBottom, DRAWABLE_UBER_BADGE) + .addAttribute(android.R.attr.drawablePadding, ONE_SP) + .build(); UberButton uberButton = new UberButton(context, attributeSet, 0, 0) { }; Drawable[] drawables = uberButton.getCompoundDrawables(); @@ -122,9 +99,9 @@ public void onCreate_whenCompoundDrawablesAndPaddingSet_shouldSetCompoundDrawabl @Test public void onCreate_whenOverallPaddingSet_shouldAddOverallPadding() { - AttributeSet attributeSet = makeAttributeSet( - makeAttribute(ANDROID_ATTR_PADDING, ONE_SP) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.padding, ONE_SP) + .build(); UberButton uberButton = new UberButton(context, attributeSet, 0, 0) { }; assertEquals(1, uberButton.getPaddingLeft()); assertEquals(1, uberButton.getPaddingTop()); @@ -134,12 +111,13 @@ public void onCreate_whenOverallPaddingSet_shouldAddOverallPadding() { @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) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.paddingLeft, ONE_SP) + .addAttribute(android.R.attr.paddingTop, TWO_SP) + .addAttribute(android.R.attr.paddingRight, THREE_SP) + .addAttribute(android.R.attr.paddingBottom, FOUR_SP) + .build(); + UberButton uberButton = new UberButton(context, attributeSet, 0, 0) { }; assertEquals(1, uberButton.getPaddingLeft()); assertEquals(2, uberButton.getPaddingTop()); @@ -149,11 +127,12 @@ public void onCreate_whenIndividualPaddingsSet_shouldHaveSeparatePaddings() { @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) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.padding, ONE_SP) + .addAttribute(android.R.attr.paddingTop, TWO_SP) + .addAttribute(android.R.attr.paddingBottom, FOUR_SP) + .build(); + UberButton uberButton = new UberButton(context, attributeSet, 0, 0) { }; assertEquals(1, uberButton.getPaddingLeft()); assertEquals(2, uberButton.getPaddingTop()); @@ -163,13 +142,14 @@ public void onCreate_whenIndividualAndOverallPaddingsSet_shouldHaveIndividualPad @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) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(android.R.attr.textColor, ANDROID_COLOR_BLACK) + .addAttribute(android.R.attr.gravity, GRAVITY_END) + .addAttribute(android.R.attr.textSize, FOUR_SP) + .addAttribute(android.R.attr.textStyle, STYLE_ITALIC) + .addAttribute(android.R.attr.text, TEXT) + .build(); + UberButton uberButton = new UberButton(context, attributeSet, 0, 0) { }; assertEquals(Color.BLACK, uberButton.getCurrentTextColor()); assertEquals(Typeface.ITALIC, uberButton.getTypeface().getStyle()); @@ -203,9 +183,9 @@ public void onCreate_whenNoAttributesSet_shouldUseUberButtonDefaults() { @Test public void onCreate_whenUberStyleSet_shouldUseUberStyle() { - AttributeSet attributeSet = makeAttributeSet( - makeAttribute(UBER_ATTR_UBER_STYE, "white") - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(R.attr.ub__style, "white") + .build(); UberButton uberButton = new UberButton(context, attributeSet, 0, R.style.UberButton_White) { }; Resources resources = context.getResources(); @@ -266,12 +246,4 @@ public void getActivity_whenContextIsNotActivity_shouldThrowException() { uberButton.getActivity(); } - - 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/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index cef33764..bc914417 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -29,6 +29,7 @@ import android.util.AttributeSet; import com.google.common.collect.Sets; +import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.rides.client.SessionConfiguration; @@ -37,11 +38,6 @@ import org.junit.Test; import org.mockito.Mock; import org.robolectric.Robolectric; -import org.robolectric.res.Attribute; -import org.robolectric.shadows.CoreShadowsAdapter; -import org.robolectric.shadows.RoboAttributeSet; - -import java.util.Arrays; import java.util.HashSet; import static org.assertj.core.api.Assertions.assertThat; @@ -54,7 +50,6 @@ public class LoginButtonTest extends RobolectricTestBase { - private static final String UBER_PACKAGE_NAME = "com.uber.sdk.android.core"; private static final HashSet SCOPES = Sets.newHashSet(Scope.HISTORY, Scope.REQUEST_RECEIPT); private static final int REQUEST_CODE = 11133; private Activity activity; @@ -100,9 +95,9 @@ public void testButtonClickWithoutRequestCode_shouldUseDefaultCode() { @Test public void testButtonClickWithScopesFromXml_shouldUseParseScopes() { - AttributeSet attributeSet = makeAttributeSet( - makeAttribute(UBER_PACKAGE_NAME + ":attr/ub__scopes", "history|request_receipt") - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(R.attr.ub__scopes, "history|request_receipt") + .build(); loginButton = new TestLoginButton(activity, attributeSet, loginManager); loginButton.setCallback(loginCallback); @@ -114,10 +109,10 @@ public void testButtonClickWithScopesFromXml_shouldUseParseScopes() { @Test public void testButtonClickWithScopesRequestCodeFromXml_shouldUseParseAll() { - AttributeSet attributeSet = makeAttributeSet( - makeAttribute(UBER_PACKAGE_NAME + ":attr/ub__scopes", "history|request_receipt"), - makeAttribute(UBER_PACKAGE_NAME + ":attr/ub__request_code", REQUEST_CODE) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(R.attr.ub__scopes, "history|request_receipt") + .addAttribute(R.attr.ub__request_code, String.valueOf(REQUEST_CODE)) + .build(); loginButton = new TestLoginButton(activity, attributeSet, loginManager); loginButton.setCallback(loginCallback); @@ -129,10 +124,10 @@ public void testButtonClickWithScopesRequestCodeFromXml_shouldUseParseAll() { @Test public void testButtonClickWithoutLoginManager_shouldCreateNew() { - AttributeSet attributeSet = makeAttributeSet( - makeAttribute(UBER_PACKAGE_NAME + ":attr/ub__scopes", "history|request_receipt"), - makeAttribute(UBER_PACKAGE_NAME + ":attr/ub__request_code", REQUEST_CODE) - ); + AttributeSet attributeSet = Robolectric.buildAttributeSet() + .addAttribute(R.attr.ub__scopes, "history|request_receipt") + .addAttribute(R.attr.ub__request_code, String.valueOf(REQUEST_CODE)) + .build(); loginButton = new LoginButton(activity, attributeSet); loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()); @@ -191,12 +186,4 @@ protected LoginManager getOrCreateLoginManager() { return manager; } } - - 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); - } } \ No newline at end of file diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index ad002a09..59cfc7ae 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -91,11 +91,11 @@ public class LoginManagerTest extends RobolectricTestBase { private static final ImmutableList MIXED_SCOPES = ImmutableList.of(Scope.PROFILE, Scope.REQUEST_RECEIPT); private static final ImmutableList GENERAL_SCOPES = ImmutableList.of(Scope.PROFILE, Scope.HISTORY); - private static final String WORLD_REGION = - "uber://connect?client_id=Client1234&scope=profile%20request_receipt&login_type=WORLD&sdk=android&sdk_version=" + private static final String DEFAULT_REGION = + "uber://connect?client_id=Client1234&scope=profile%20request_receipt&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; - private static final String INSTALL = "https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v0.5.4-login_manager"; + private static final String INSTALL = "https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v0.6.0-login_manager"; private static final String AUTHORIZATION_CODE = "Auth123Code"; @Mock @@ -125,7 +125,7 @@ public void setup() { @Test public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAME, SsoDeeplink.MIN_VERSION_SUPPORTED); + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); loginManager.login(activity); @@ -134,7 +134,7 @@ public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(WORLD_REGION); + assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(DEFAULT_REGION); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -142,7 +142,7 @@ public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchIntent() { loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration, REQUEST_CODE); - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAME, SsoDeeplink.MIN_VERSION_SUPPORTED); + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); loginManager.login(activity); @@ -151,7 +151,7 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(WORLD_REGION); + assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(DEFAULT_REGION); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); } @@ -160,7 +160,7 @@ public void loginWithoutAppInstalledGeneralScopes_shouldLaunchWebView() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration); - stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAME); + stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); loginManager.login(activity); @@ -185,7 +185,7 @@ public void loginWithoutAppInstalledPrivilegedScopes_shouldLaunchAppInstall() { loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration) .setRedirectForAuthorizationCode(false); - stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAME); + stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); loginManager.login(activity); @@ -371,6 +371,7 @@ private static PackageManager stubAppInstalled(PackageManager packageManager, St final PackageInfo packageInfo = new PackageInfo(); packageInfo.versionCode = versionCode; packageInfo.signatures = new Signature[]{new Signature(PUBLIC_SIGNATURE)}; + packageInfo.packageName = packageName; try { when(packageManager.getPackageInfo(eq(packageName), anyInt())) .thenReturn(packageInfo); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java index cfe7fd57..0d72ddd8 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java @@ -84,24 +84,6 @@ public void onBuildUrl_withDefaultRegion_shouldHaveDefaultUberDomain() { "show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A", url); } - @Test - public void onBuildUrl_withChinaRegion_shouldHaveChinaDomain() { - String clientId = "clientId1234"; - String redirectUri = "localHost1234"; - - SessionConfiguration loginConfiguration = new SessionConfiguration.Builder() - .setRedirectUri(redirectUri) - .setClientId(clientId) - .setScopes(Arrays.asList(Scope.HISTORY)) - .setEndpointRegion(SessionConfiguration.EndpointRegion.CHINA).build(); - - String url = testLoginActivity.buildUrl(redirectUri, ResponseType.TOKEN, loginConfiguration); - assertEquals( - "https://login.uber.com.cn/oauth/v2/authorize?client_id=" + clientId + - "&redirect_uri=" + redirectUri + "&response_type=token&scope=history" + - "&show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A", url); - } - @Test public void onLoadLoginView_withNoRedirectUrl_shouldReturnError() { SessionConfiguration config = new SessionConfiguration.Builder().setClientId("clientId").build(); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 7c1792e9..78782dae 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -34,7 +34,6 @@ import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; @@ -50,7 +49,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyCollection; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -64,11 +62,8 @@ public class SsoDeeplinkTest extends RobolectricTestBase { private static final Set GENERAL_SCOPES = Sets.newHashSet(Scope.HISTORY, Scope.PROFILE); private static final int REQUEST_CODE = 1234; - private static final String CHINA__REGION = - "uber://connect?client_id=MYCLIENTID&scope=profile%20history&login_type=CHINA&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; - - private static final String WORLD_REGION = - "uber://connect?client_id=MYCLIENTID&scope=profile%20history&login_type=WORLD&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; + private static final String DEFAULT_REGION = + "uber://connect?client_id=MYCLIENTID&scope=profile%20history&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; @Mock PackageManager packageManager; @@ -120,7 +115,7 @@ public void testIsSupported_appInstalledButOldVersion_shouldBeFalse() { when(activity.getPackageManager()).thenReturn(packageManager); try { - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAME, 0)).thenReturn(packageInfo); + when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], 0)).thenReturn(packageInfo); } catch (PackageManager.NameNotFoundException e) { fail("Unable to mock Package Manager"); } @@ -140,7 +135,7 @@ public void testIsSupported_appInstalledButOldVersion_shouldBeFalse() { public void testIsSupported_noAppInstalled_shouldBeFalse() { when(activity.getPackageManager()).thenReturn(packageManager); try { - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAME, PackageManager.GET_META_DATA)) + when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_META_DATA)) .thenThrow(PackageManager.NameNotFoundException.class); } catch (PackageManager.NameNotFoundException e) { fail("Unable to mock Package Manager"); @@ -157,29 +152,6 @@ public void testIsSupported_noAppInstalled_shouldBeFalse() { assertThat(isSupported).isFalse(); } - @Test - public void testInvokeWithAllParams_shouldContainsFullUri() { - enableSupport(); - - final SsoDeeplink link = new SsoDeeplink.Builder(activity) - .clientId(CLIENT_ID) - .region(SessionConfiguration.EndpointRegion.CHINA) - .scopes(GENERAL_SCOPES) - .activityRequestCode(REQUEST_CODE) - .build(); - link.appProtocol = protocol; - link.execute(); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); - verify(activity).startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture()); - - final Uri uri = intentCaptor.getValue().getData(); - - assertThat(uri.toString()).isEqualTo(CHINA__REGION); - assertThat(requestCodeCaptor.getValue()).isEqualTo(REQUEST_CODE); - } - @Test public void testInvokeWithoutRegion_shouldUseWorld() { enableSupport(); @@ -199,7 +171,7 @@ public void testInvokeWithoutRegion_shouldUseWorld() { final Uri uri = intentCaptor.getValue().getData(); - assertThat(uri.toString()).isEqualTo(WORLD_REGION); + assertThat(uri.toString()).isEqualTo(DEFAULT_REGION); assertThat(requestCodeCaptor.getValue()).isEqualTo(REQUEST_CODE); } @@ -221,7 +193,7 @@ public void testInvokeWithoutRequestCode_shouldUseDefaultRequstCode() { Uri uri = intentCaptor.getValue().getData(); - assertThat(uri.toString()).isEqualTo(WORLD_REGION); + assertThat(uri.toString()).isEqualTo(DEFAULT_REGION); assertThat(requestCodeCaptor.getValue()).isEqualTo(LoginManager.REQUEST_CODE_LOGIN_DEFAULT); } @@ -232,7 +204,6 @@ public void testInvokeWithoutScopes_shouldFail() { final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) - .region(SessionConfiguration.EndpointRegion.WORLD) .activityRequestCode(REQUEST_CODE) .build(); @@ -249,7 +220,6 @@ public void testInvokeWithScopesAndCustomScopes_shouldSucceed() { final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) - .region(SessionConfiguration.EndpointRegion.WORLD) .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) .customScopes(collection) @@ -270,7 +240,6 @@ public void testInvokeWithoutClientId_shouldFail() { final SsoDeeplink link = new SsoDeeplink.Builder(activity) .scopes(GENERAL_SCOPES) - .region(SessionConfiguration.EndpointRegion.WORLD) .activityRequestCode(REQUEST_CODE) .build(); @@ -282,7 +251,7 @@ public void testInvokeWithoutClientId_shouldFail() { public void testInvokeWithoutAppInstalled_shouldFail() { when(activity.getPackageManager()).thenReturn(packageManager); try { - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAME, PackageManager.GET_META_DATA)) + when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_META_DATA)) .thenThrow(PackageManager.NameNotFoundException.class); } catch (PackageManager.NameNotFoundException e) { fail("Unable to mock Package Manager"); @@ -302,11 +271,12 @@ private void enableSupport() { when(activity.getPackageManager()).thenReturn(packageManager); try { - when(packageManager.getPackageInfo(eq(AppProtocol.UBER_PACKAGE_NAME), anyInt())) + when(packageManager.getPackageInfo(eq(AppProtocol.UBER_PACKAGE_NAMES[0]), anyInt())) .thenReturn(packageInfo); } catch (PackageManager.NameNotFoundException e) { fail("Unable to mock Package Manager"); } when(protocol.validateSignature(any(Context.class), anyString())).thenReturn(true); + when(protocol.validateMinimumVersion(any(Context.class), any(PackageInfo.class), anyInt())).thenReturn(true); } } \ No newline at end of file diff --git a/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java b/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java index 74f6ecd1..508ace04 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java @@ -73,19 +73,19 @@ public void setUp() throws Exception { @Test public void validateSignature_whenValid_returnsTrue() throws Exception { stubAppSignature(GOOD_SIGNATURE); - assertTrue(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAME)); + assertTrue(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAMES[0])); } @Test public void validateSignature_whenInvalid_returnsFalse() throws Exception { stubAppSignature(BAD_SIGNATURE); - assertFalse(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAME)); + assertFalse(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAMES[0])); } @Test public void validateSignature_whenGoodAndBad_returnsFalse() throws Exception { stubAppSignature(GOOD_SIGNATURE, BAD_SIGNATURE); - assertFalse(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAME)); + assertFalse(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAMES[0])); } @Test @@ -98,7 +98,8 @@ public void getApplicationSignature_whenValidPackageSignature_shouldSucceed() th public void getPackageSignature_whenNameNotFoundException_shouldReturnNull() throws Exception { stubAppSignature(GOOD_SIGNATURE); final Throwable throwable = new PackageManager.NameNotFoundException(); - doThrow(throwable).when(packageManager).getPackageInfo(AppProtocol.UBER_PACKAGE_NAME, PackageManager.GET_SIGNATURES); + doThrow(throwable).when(packageManager) + .getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_SIGNATURES); assertThat(appProtocol.getAppSignature(activity)).isNull(); } @@ -106,7 +107,7 @@ public void getPackageSignature_whenNameNotFoundException_shouldReturnNull() thr @Test public void getPackageSignature_whenNullPackageInfo_shouldReturnNull() throws Exception { stubAppSignature(GOOD_SIGNATURE); - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAME, PackageManager.GET_SIGNATURES)) + when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_SIGNATURES)) .thenReturn(null); assertThat(appProtocol.getAppSignature(activity)).isNull(); @@ -132,7 +133,7 @@ public void getPackageSignature_whenNoSuchAlgorithmException_shouldReturnNull() } private void stubAppSignature(String... sig) throws Exception { - when(activity.getPackageName()).thenReturn(AppProtocol.UBER_PACKAGE_NAME); + when(activity.getPackageName()).thenReturn(AppProtocol.UBER_PACKAGE_NAMES[0]); when(activity.getPackageManager()).thenReturn(packageManager); Signature[] signatures = new Signature[sig.length]; @@ -143,7 +144,7 @@ private void stubAppSignature(String... sig) throws Exception { packageInfo.signatures = signatures; try { - when(packageManager.getPackageInfo(eq(AppProtocol.UBER_PACKAGE_NAME), anyInt())) + when(packageManager.getPackageInfo(eq(AppProtocol.UBER_PACKAGE_NAMES[0]), anyInt())) .thenReturn(packageInfo); } catch (PackageManager.NameNotFoundException e) { fail("Unable to mock Package Manager"); diff --git a/gradle.properties b/gradle.properties index fe518a73..a7b9eb19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ group=com.uber.sdk groupId=com.uber.sdk artifactId=rides-android githubDownloadPrefix=https://github.com/uber/rides-android-sdk/releases/download/ -version=0.5.5-SNAPSHOT +version=0.6.0 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5b7e6468..92edd979 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 12 19:04:32 PDT 2015 +#Fri Feb 10 13:49:35 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/rides-android/build.gradle b/rides-android/build.gradle index 0f3a728f..0c6a3d52 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -10,7 +10,7 @@ buildscript { } dependencies { - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' } } @@ -168,7 +168,7 @@ dependencies { testCompile 'com.google.http-client:google-http-client-jackson2:1.19.0' testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'org.assertj:assertj-core:1.7.1' - testCompile 'org.robolectric:robolectric:3.0' + testCompile 'org.robolectric:robolectric:3.2.2' testCompile ('com.github.tomakehurst:wiremock:2.0.10-beta') { exclude module:'asm' } diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java index 524a0edc..325a0c17 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java @@ -46,7 +46,7 @@ */ public class RequestDeeplink implements Deeplink { - private static final String USER_AGENT_DEEPLINK = "rides-android-v0.5.4-deeplink"; + private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.0-deeplink"; @NonNull private final Uri uri; @@ -78,7 +78,12 @@ public void execute() { @Override public boolean isSupported() { - return PackageManagers.isPackageAvailable(context, AppProtocol.UBER_PACKAGE_NAME); + for (String packageName : AppProtocol.UBER_PACKAGE_NAMES) { + if(PackageManagers.isPackageAvailable(context, packageName)) { + return true; + } + } + return false; } /** diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index 1f01570c..e821d151 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -68,7 +68,7 @@ public class RideRequestActivity extends Activity implements LoginCallback, Ride static final int LOGIN_REQUEST_CODE = 1112; private static final int REQUEST_FINE_LOCATION_PERMISSION_CODE = 1002; - private static final String USER_AGENT_RIDE_WIDGET = "rides-android-v0.5.4-ride_request_widget"; + private static final String USER_AGENT_RIDE_WIDGET = "rides-android-v0.6.0-ride_request_widget"; @VisibleForTesting static final String RIDE_PARAMETERS = "ride_parameters"; static final String EXTRA_LOGIN_CONFIGURATION = "login_configuration"; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java index 7ccd097a..58222078 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java @@ -61,7 +61,7 @@ public class RideRequestButton extends FrameLayout implements RideRequestButtonV @StyleRes int[] STYLES = {R.style.UberButton, R.style.UberButton_White}; - private static final String USER_AGENT_BUTTON = "rides-android-v0.5.4-button"; + private static final String USER_AGENT_BUTTON = "rides-android-v0.6.0-button"; private RideRequestBehavior rideRequestBehavior; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java index 5b49ddb0..9142bfe5 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java @@ -55,7 +55,7 @@ */ public class RideRequestView extends LinearLayout { - private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.5.4-ride_request_view"; + private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.0-ride_request_view"; @Nullable private AccessTokenSession accessTokenSession; @NonNull @VisibleForTesting RideParameters rideParameters = new RideParameters.Builder().build(); @Nullable private RideRequestViewCallback rideRequestViewCallback; @@ -167,7 +167,7 @@ static String buildUrlFromRideParameters(@NonNull Context context, Uri.Builder builder = new Uri.Builder(); builder.scheme(HTTPS) - .authority(ENDPOINT + "." + loginConfiguration.getEndpointRegion().domain) + .authority(ENDPOINT + "." + loginConfiguration.getEndpointRegion().getDomain()) .appendEncodedPath(PATH); if (rideParameters.getUserAgent() == null) { diff --git a/rides-android/src/test/java/android/net/http/AndroidHttpClient.java b/rides-android/src/test/java/android/net/http/AndroidHttpClient.java deleted file mode 100644 index 4d44478a..00000000 --- a/rides-android/src/test/java/android/net/http/AndroidHttpClient.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2016 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 android.net.http; - -/** - * Apache HTTP Client was deprecated and is no longer part of compile classpath. - * Robolectic still links to it so an empty class is needed for compiling purposes. - * - * See Robolectric issue for - * details. - */ -public class AndroidHttpClient { } diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java index 48d2d4b4..0a294a38 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; import org.robolectric.res.builder.RobolectricPackageManager; import org.robolectric.shadows.ShadowActivity; @@ -56,7 +57,7 @@ public class RequestDeeplinkTest extends RobolectricTestBase { private static final Double DROPOFF_LONG = -122.6789; private static final String DROPOFF_NICK = "pickupNick"; private static final String DROPOFF_ADDR = "Dropoff Address"; - private static final String USER_AGENT_DEEPLINK = "rides-android-v0.5.4-deeplink"; + private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.0-deeplink"; private Context context; @@ -179,7 +180,7 @@ public void execute_whenUberAppInsalled_shouldPointToUberApp() throws IOExceptio Activity activity = Robolectric.setupActivity(Activity.class); ShadowActivity shadowActivity = shadowOf(activity); - RobolectricPackageManager packageManager = (RobolectricPackageManager) shadowActivity.getPackageManager(); + RobolectricPackageManager packageManager = RuntimeEnvironment.getRobolectricPackageManager(); PackageInfo uberPackage = new PackageInfo(); uberPackage.packageName = UBER_PACKAGE_NAME; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index 050a1707..beb9bc87 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -121,13 +121,13 @@ public void onLoad_whenNullUserAgent_shouldAddRideWidgetUserAgent() { "refreshToken", "tokenType"); activity.onLoginSuccess(accessToken); - assertEquals("rides-android-v0.5.4-ride_request_widget", + assertEquals("rides-android-v0.6.0-ride_request_widget", activity.rideRequestView.rideParameters.getUserAgent()); } @Test public void onLoad_withUserAgentInRideParametersButton_shouldNotGetOverridden() { - String userAgent = "rides-android-v0.5.4-button"; + String userAgent = "rides-android-v0.6.0-button"; RideParameters rideParameters = new RideParameters.Builder().build(); rideParameters.setUserAgent(userAgent); Intent data = RideRequestActivity.newIntent(Robolectric.setupActivity(Activity.class), diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java index 0d84f971..821b49eb 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java @@ -74,7 +74,7 @@ public class RideRequestViewTest extends RobolectricTestBase { private static final String DROPOFF_NICK = "pickupNick"; private static final String DROPOFF_ADDR = "Dropoff Address"; private static final String TOKEN_STRING = "thisIsAnAccessToken"; - private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.5.4-ride_request_view"; + private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.0-ride_request_view"; private AccessToken accessToken; private RideRequestView rideRequestView; @@ -103,27 +103,12 @@ public void onBuildUrl_inDefaultRegion_shouldHaveUrlWithDefaultDomain() throws I SessionConfiguration configuration = new SessionConfiguration.Builder() .setClientId("clientId") - .setEndpointRegion(SessionConfiguration.EndpointRegion.WORLD) .build(); String result = RideRequestView.buildUrlFromRideParameters(context, rideParameters, configuration); assertEquals(expectedUri, result); } - @Test - public void onBuildUrl_inChinaRegion_shouldHaveUrlWithChinaDomain() throws IOException { - String path = "src/test/resources/riderequestviewuris/china_uri"; - String expectedUri = readUriResourceWithUserAgentParam(path, USER_AGENT_RIDE_VIEW); - - RideParameters rideParameters = new RideParameters.Builder().build(); - SessionConfiguration configuration = new SessionConfiguration.Builder() - .setClientId("clientId") - .setEndpointRegion(SessionConfiguration.EndpointRegion.CHINA) - .build(); - - String result = RideRequestView.buildUrlFromRideParameters(context, rideParameters, configuration); - assertEquals(expectedUri, result); - } @Test public void onBuildUrl_inSandboxMode_shouldHaveUrlWithSandboxParam() throws IOException { @@ -131,7 +116,6 @@ public void onBuildUrl_inSandboxMode_shouldHaveUrlWithSandboxParam() throws IOEx SessionConfiguration configuration = new SessionConfiguration.Builder() .setClientId("clientId") - .setEndpointRegion(SessionConfiguration.EndpointRegion.WORLD) .setEnvironment(SessionConfiguration.Environment.SANDBOX) .build(); @@ -152,7 +136,6 @@ public void onBuildUrl_withRideParams_shouldHaveRideParamsQueryParams() throws I .build(); SessionConfiguration configuration = new SessionConfiguration.Builder() .setClientId("clientId") - .setEndpointRegion(SessionConfiguration.EndpointRegion.WORLD) .build(); String result = RideRequestView.buildUrlFromRideParameters(context, rideParameters, configuration); @@ -161,7 +144,7 @@ public void onBuildUrl_withRideParams_shouldHaveRideParamsQueryParams() throws I @Test public void onBuildUrl_withUserAgentNonNull_shouldNotOverride() throws IOException { - String widgetUserAgent = "rides-android-v0.5.4-ride_request_widget"; + String widgetUserAgent = "rides-android-v0.6.0-ride_request_widget"; String path = "src/test/resources/riderequestviewuris/default_uri"; String expectedUri = readUriResourceWithUserAgentParam(path, widgetUserAgent); @@ -170,7 +153,6 @@ public void onBuildUrl_withUserAgentNonNull_shouldNotOverride() throws IOExcepti SessionConfiguration configuration = new SessionConfiguration.Builder() .setClientId("clientId") - .setEndpointRegion(SessionConfiguration.EndpointRegion.WORLD) .build(); String result = RideRequestView.buildUrlFromRideParameters(context, rideParameters, configuration); @@ -265,6 +247,6 @@ public void shouldOverrideUrlLoading_whenNonHttpOrRedirect_shouldOverrideAndLaun verifyZeroInteractions(callback); Intent startedIntent = shadowActivity.getNextStartedActivity(); assertEquals(Intent.ACTION_VIEW, startedIntent.getAction()); - assertEquals("tel:+91555555555#Intent;end", startedIntent.toUri(0)); + assertEquals("tel:+91555555555#Intent;action=android.intent.action.VIEW;end", startedIntent.toUri(0)); } } diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java index 271b1548..b883522b 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java @@ -23,13 +23,13 @@ package com.uber.sdk.android.rides; import org.junit.runner.RunWith; -import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; /** * Base test class to remove use of annotations on all test classes. */ -@RunWith(RobolectricGradleTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) public abstract class RobolectricTestBase { } diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java index 180d8a77..a1f79675 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java @@ -32,6 +32,7 @@ import com.uber.sdk.android.rides.RideParameters; import com.uber.sdk.android.rides.RideRequestButtonCallback; import com.uber.sdk.rides.client.error.ApiError; +import com.uber.sdk.rides.client.internal.BigDecimalAdapter; import com.uber.sdk.rides.client.model.PriceEstimate; import com.uber.sdk.rides.client.model.PriceEstimatesResponse; import com.uber.sdk.rides.client.model.TimeEstimate; @@ -78,8 +79,8 @@ @RunWith(MockitoJUnitRunner.class) public class RideRequestButtonControllerTest { - private static final String TIME_ESTIMATES_API = "/v1/estimates/time"; - private static final String PRICE_ESTIMATES_API = "/v1/estimates/price"; + private static final String TIME_ESTIMATES_API = "/v1.2/estimates/time"; + private static final String PRICE_ESTIMATES_API = "/v1.2/estimates/price"; private static WireMockConfiguration WIRE_MOCK_CONFIG = wireMockConfig() .notifier(new ConsoleNotifier(true)) .dynamicPort(); @@ -135,6 +136,7 @@ public void setUp() throws Exception { .build(); Moshi moshi = new Moshi.Builder() + .add(new BigDecimalAdapter()) .build(); okHttpClient = new OkHttpClient.Builder() diff --git a/rides-android/src/test/resources/__files/prices_estimate.json b/rides-android/src/test/resources/__files/prices_estimate.json index bd1f2d56..006bff1c 100644 --- a/rides-android/src/test/resources/__files/prices_estimate.json +++ b/rides-android/src/test/resources/__files/prices_estimate.json @@ -2,105 +2,105 @@ "prices": [ { "localized_display_name": "uberPOOL", - "high_estimate": 6, + "high_estimate": 6.0, "minimum": null, "duration": 1080, "estimate": "$5.75", "distance": 2.5, "display_name": "uberPOOL", "product_id": "26546650-e557-4a7b-86e7-6a3942445247", - "low_estimate": 5, + "low_estimate": 5.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "uberX", - "high_estimate": 12, + "high_estimate": 12.0, "minimum": 7, "duration": 1080, "estimate": "$9-12", "distance": 2.5, "display_name": "uberX", "product_id": "a1111c8c-c720-46c3-8534-2fcdd730040d", - "low_estimate": 9, + "low_estimate": 9.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "uberXL", - "high_estimate": 18, + "high_estimate": 18.0, "minimum": 9, "duration": 1080, "estimate": "$14-18", "distance": 2.5, "display_name": "uberXL", "product_id": "821415d8-3bd5-4e27-9604-194e4359a449", - "low_estimate": 14, + "low_estimate": 14.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "UberSELECT", - "high_estimate": 26, + "high_estimate": 26.0, "minimum": 11, "duration": 1080, "estimate": "$21-26", "distance": 2.5, "display_name": "UberSELECT", "product_id": "57c0ff4e-1493-4ef9-a4df-6b961525cf92", - "low_estimate": 21, + "low_estimate": 21.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "UberBLACK", - "high_estimate": 34, + "high_estimate": 34.0, "minimum": 15, "duration": 1080, "estimate": "$27-34", "distance": 2.5, "display_name": "UberBLACK", "product_id": "d4abaae7-f4d6-4152-91cc-77523e8165a4", - "low_estimate": 27, + "low_estimate": 27.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "UberSUV", - "high_estimate": 47, + "high_estimate": 47.0, "minimum": 25, "duration": 1080, "estimate": "$38-47", "distance": 2.5, "display_name": "UberSUV", "product_id": "8920cb5e-51a4-4fa4-acdf-dd86c5e18ae0", - "low_estimate": 38, + "low_estimate": 38.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "ASSIST", - "high_estimate": 12, + "high_estimate": 12.0, "minimum": 7, "duration": 1080, "estimate": "$9-12", "distance": 2.5, "display_name": "ASSIST", "product_id": "ff5ed8fe-6585-4803-be13-3ca541235de3", - "low_estimate": 9, + "low_estimate": 9.0, "surge_multiplier": 1.0, "currency_code": "USD" }, { "localized_display_name": "uberWAV", - "high_estimate": 23, + "high_estimate": 23.0, "minimum": 9, "duration": 1080, "estimate": "$18-23", "distance": 2.5, "display_name": "uberWAV", "product_id": "2832a1f5-cfc0-48bb-ab76-7ea7a62060e7", - "low_estimate": 18, + "low_estimate": 18.0, "surge_multiplier": 1.0, "currency_code": "USD" }, diff --git a/samples/login-sample/build.gradle b/samples/login-sample/build.gradle index 739f97f5..e4edf35e 100644 --- a/samples/login-sample/build.gradle +++ b/samples/login-sample/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle index 06388fbd..f1261cbe 100644 --- a/samples/request-button-sample/build.gradle +++ b/samples/request-button-sample/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } 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 index 4516f8e8..449b551b 100644 --- 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 @@ -85,10 +85,6 @@ protected void onCreate(Bundle savedInstanceState) { .setServerToken(SERVER_TOKEN) .build(); - // Optional: to use the SDK in China, set the region property - // See https://developer.uber.com/docs/china for more details. - // configuration.setEndpointRegion(SessionConfiguration.EndpointRegion.CHINA); - validateConfiguration(configuration); ServerTokenSession session = new ServerTokenSession(configuration); diff --git a/test-shared/test/java/com/uber/sdk/android/core/RobolectricTestBase.java b/test-shared/test/java/com/uber/sdk/android/core/RobolectricTestBase.java index 0e4b6fda..be796f8c 100644 --- a/test-shared/test/java/com/uber/sdk/android/core/RobolectricTestBase.java +++ b/test-shared/test/java/com/uber/sdk/android/core/RobolectricTestBase.java @@ -26,10 +26,10 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -@RunWith(RobolectricGradleTestRunner.class) +@RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) public abstract class RobolectricTestBase { diff --git a/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java b/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java index 3be9e4f5..467445a3 100644 --- a/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java +++ b/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java @@ -54,7 +54,7 @@ public String getRedirectUri() { @NonNull public SessionConfiguration.EndpointRegion getRegion() { - return SessionConfiguration.EndpointRegion.valueOf(sharedPreferences.getString(REGION_KEY, SessionConfiguration.EndpointRegion.WORLD.name())); + return SessionConfiguration.EndpointRegion.valueOf(sharedPreferences.getString(REGION_KEY, SessionConfiguration.EndpointRegion.DEFAULT.name())); } @Nullable From 43d9b181bfc5fb3fa4bdf54c36cd3b05b1fecc94 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 14 Feb 2017 18:04:40 -0800 Subject: [PATCH 002/165] [Gradle Release Plugin] - new version commit: 'v0.6.0'. --- CHANGELOG.md | 3 +++ gradle.properties | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217b523e..473e1adc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.6.1 - TBD +------------ + v0.6.0 - 2/14/2017 ------------------- ### Fixed diff --git a/gradle.properties b/gradle.properties index a7b9eb19..a3ff17a8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ group=com.uber.sdk groupId=com.uber.sdk artifactId=rides-android githubDownloadPrefix=https://github.com/uber/rides-android-sdk/releases/download/ -version=0.6.0 +version=0.6.1-SNAPSHOT From f11ba265077b84405ad6e88a45017c40af8099e3 Mon Sep 17 00:00:00 2001 From: Andrew Noonan Date: Wed, 29 Mar 2017 15:27:56 -0700 Subject: [PATCH 003/165] fix link fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1122e83e..e2b78464 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ If you want to provide a more custom experience in your app, there are a few cla ### Login The Uber SDK allows for three login flows: Implicit Grant (local web view), Single Sign On with the Uber App, and Authorization Code Grant (requires a backend to catch the local web view redirect and complete OAuth). -To use Single Sign On you must register a hash of your application's signing certificate in the Application Signature section of the [developer dashboard] (https://developer.uber.com/dashboard). +To use Single Sign On you must register a hash of your application's signing certificate in the Application Signature section of the [developer dashboard](https://developer.uber.com/dashboard). To get the hash of your signing certificate, run this command with the alias of your key and path to your keystore: From b6335d58ea2afddd6c919dc660c23d5634b923a2 Mon Sep 17 00:00:00 2001 From: Alex Texter Date: Wed, 5 Apr 2017 17:54:48 -0700 Subject: [PATCH 004/165] [Gradle Release Plugin] - pre tag commit: 'v0.6.1'. --- CHANGELOG.md | 6 ++++-- README.md | 6 +++--- .../com/uber/sdk/android/core/auth/AuthUtils.java | 6 +++++- .../uber/sdk/android/core/auth/LoginManager.java | 2 +- .../uber/sdk/android/core/auth/AuthUtilsTest.java | 15 ++++++++++----- .../sdk/android/core/auth/LoginManagerTest.java | 2 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../uber/sdk/android/rides/RequestDeeplink.java | 2 +- .../sdk/android/rides/RideRequestActivity.java | 2 +- .../uber/sdk/android/rides/RideRequestButton.java | 2 +- .../uber/sdk/android/rides/RideRequestView.java | 2 +- .../sdk/android/rides/RequestDeeplinkTest.java | 2 +- .../android/rides/RideRequestActivityTest.java | 4 ++-- .../sdk/android/rides/RideRequestViewTest.java | 4 ++-- 15 files changed, 35 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 473e1adc..8d11f5d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -v0.6.1 - TBD ------------- +v0.6.1 - 4/5/2017 +------------------- +### Changed + - AuthUtils now omits unrecognized scopes from parsed AccessToken instead of throwing an exception when creating v0.6.0 - 2/14/2017 ------------------- diff --git a/README.md b/README.md index e2b78464..392a5aab 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ To use the Uber Rides Android SDK, add the compile dependency with the latest ve Add the Uber Rides Android SDK to your `build.gradle`: ```gradle dependencies { - compile 'com.uber.sdk:rides-android:0.6.0' + compile 'com.uber.sdk:rides-android:0.6.1' } ``` @@ -31,7 +31,7 @@ In the `pom.xml` file: com.uber.sdk rides-android - 0.6.0 + 0.6.1 ``` @@ -319,7 +319,7 @@ service.getUserProfile().enqueue(new Callback() { ## Sample Apps -Sample apps 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.6.0). +Sample apps 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.6.1). The Sample apps require configuration parameters to interact with the Uber API, these include the client id, redirect uri, and server token. They are provided on the [Uber developer dashboard](https://developer.uber.com/dashboard). diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index d5a7e17c..6ff243c8 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -91,7 +91,11 @@ static Collection stringToScopeCollection(@NonNull String scopesString) t String[] scopeStrings = scopesString.split(" "); for (String scopeName : scopeStrings) { - scopeCollection.add(Scope.valueOf(scopeName.toUpperCase())); + try { + scopeCollection.add(Scope.valueOf(scopeName.toUpperCase())); + } catch (IllegalArgumentException e) { + // do nothing, will omit custom or bad scopes + } } return scopeCollection; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index cfa61285..79c700d3 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -84,7 +84,7 @@ public class LoginManager { static final int REQUEST_CODE_LOGIN_DEFAULT = 1001; - private static final String USER_AGENT = "core-android-v0.6.0-login_manager"; + private static final String USER_AGENT = "core-android-v0.6.1-login_manager"; private final AccessTokenManager accessTokenManager; private final LoginCallback callback; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index a2c485fc..a261e186 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -76,6 +76,16 @@ public void stringToScopeCollection_whenEmptyScopeString_shouldReturnEmptyScopeC assertEquals(scopes.size(), 0); } + @Test + public void stringToScopeCollection_whenUnknownScopeInString_shouldReturnOnlyKnownScopes() { + String scopeString = "profile custom"; + List scopes = new ArrayList<>(); + scopes.addAll(AuthUtils.stringToScopeCollection(scopeString)); + + assertEquals(scopes.size(), 1); + assertTrue(scopes.contains(Scope.PROFILE)); + } + @Test public void scopeCollectionToString_withMultipleScopes_shouldReturnSpaceDelimitedStringScopes() { List scopes = new ArrayList(); @@ -103,11 +113,6 @@ public void scopeCollectionToString_whenEmptyScopeCollection_shouldReturnEmptySt assertTrue(result.isEmpty()); } - @Test(expected = IllegalArgumentException.class) - public void withBadScopeString_shouldThrowException() { - AuthUtils.stringToScopeCollection("blah"); - } - @Test public void generateAccessTokenFromUrl_whenNullFragment_shouldThrowInvalidResponseError() { String redirectUri = "http://localhost:1234/"; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 59cfc7ae..98f15a97 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -95,7 +95,7 @@ public class LoginManagerTest extends RobolectricTestBase { "uber://connect?client_id=Client1234&scope=profile%20request_receipt&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; - private static final String INSTALL = "https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v0.6.0-login_manager"; + private static final String INSTALL = "https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v0.6.1-login_manager"; private static final String AUTHORIZATION_CODE = "Auth123Code"; @Mock diff --git a/gradle.properties b/gradle.properties index a3ff17a8..1a5ebdb0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ group=com.uber.sdk groupId=com.uber.sdk artifactId=rides-android githubDownloadPrefix=https://github.com/uber/rides-android-sdk/releases/download/ -version=0.6.1-SNAPSHOT +version=0.6.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 92edd979..e12a8553 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java index 325a0c17..f8e54faf 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java @@ -46,7 +46,7 @@ */ public class RequestDeeplink implements Deeplink { - private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.0-deeplink"; + private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.1-deeplink"; @NonNull private final Uri uri; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index e821d151..489eaea0 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -68,7 +68,7 @@ public class RideRequestActivity extends Activity implements LoginCallback, Ride static final int LOGIN_REQUEST_CODE = 1112; private static final int REQUEST_FINE_LOCATION_PERMISSION_CODE = 1002; - private static final String USER_AGENT_RIDE_WIDGET = "rides-android-v0.6.0-ride_request_widget"; + private static final String USER_AGENT_RIDE_WIDGET = "rides-android-v0.6.1-ride_request_widget"; @VisibleForTesting static final String RIDE_PARAMETERS = "ride_parameters"; static final String EXTRA_LOGIN_CONFIGURATION = "login_configuration"; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java index 58222078..1ba827cf 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java @@ -61,7 +61,7 @@ public class RideRequestButton extends FrameLayout implements RideRequestButtonV @StyleRes int[] STYLES = {R.style.UberButton, R.style.UberButton_White}; - private static final String USER_AGENT_BUTTON = "rides-android-v0.6.0-button"; + private static final String USER_AGENT_BUTTON = "rides-android-v0.6.1-button"; private RideRequestBehavior rideRequestBehavior; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java index 9142bfe5..ecc30e44 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java @@ -55,7 +55,7 @@ */ public class RideRequestView extends LinearLayout { - private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.0-ride_request_view"; + private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.1-ride_request_view"; @Nullable private AccessTokenSession accessTokenSession; @NonNull @VisibleForTesting RideParameters rideParameters = new RideParameters.Builder().build(); @Nullable private RideRequestViewCallback rideRequestViewCallback; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java index 0a294a38..a1fedf9b 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java @@ -57,7 +57,7 @@ public class RequestDeeplinkTest extends RobolectricTestBase { private static final Double DROPOFF_LONG = -122.6789; private static final String DROPOFF_NICK = "pickupNick"; private static final String DROPOFF_ADDR = "Dropoff Address"; - private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.0-deeplink"; + private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.1-deeplink"; private Context context; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index beb9bc87..f7a2d086 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -121,13 +121,13 @@ public void onLoad_whenNullUserAgent_shouldAddRideWidgetUserAgent() { "refreshToken", "tokenType"); activity.onLoginSuccess(accessToken); - assertEquals("rides-android-v0.6.0-ride_request_widget", + assertEquals("rides-android-v0.6.1-ride_request_widget", activity.rideRequestView.rideParameters.getUserAgent()); } @Test public void onLoad_withUserAgentInRideParametersButton_shouldNotGetOverridden() { - String userAgent = "rides-android-v0.6.0-button"; + String userAgent = "rides-android-v0.6.1-button"; RideParameters rideParameters = new RideParameters.Builder().build(); rideParameters.setUserAgent(userAgent); Intent data = RideRequestActivity.newIntent(Robolectric.setupActivity(Activity.class), diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java index 821b49eb..72168558 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java @@ -74,7 +74,7 @@ public class RideRequestViewTest extends RobolectricTestBase { private static final String DROPOFF_NICK = "pickupNick"; private static final String DROPOFF_ADDR = "Dropoff Address"; private static final String TOKEN_STRING = "thisIsAnAccessToken"; - private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.0-ride_request_view"; + private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.1-ride_request_view"; private AccessToken accessToken; private RideRequestView rideRequestView; @@ -144,7 +144,7 @@ public void onBuildUrl_withRideParams_shouldHaveRideParamsQueryParams() throws I @Test public void onBuildUrl_withUserAgentNonNull_shouldNotOverride() throws IOException { - String widgetUserAgent = "rides-android-v0.6.0-ride_request_widget"; + String widgetUserAgent = "rides-android-v0.6.1-ride_request_widget"; String path = "src/test/resources/riderequestviewuris/default_uri"; String expectedUri = readUriResourceWithUserAgentParam(path, widgetUserAgent); From f4a0420c4ba1a0076002b207aa95f3f120913d81 Mon Sep 17 00:00:00 2001 From: Alex Texter Date: Wed, 5 Apr 2017 17:55:05 -0700 Subject: [PATCH 005/165] [Gradle Release Plugin] - new version commit: 'v0.6.1'. --- CHANGELOG.md | 3 +++ gradle.properties | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d11f5d4..86ccf4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.6.2 - TBD +------------ + v0.6.1 - 4/5/2017 ------------------- ### Changed diff --git a/gradle.properties b/gradle.properties index 1a5ebdb0..cff12a48 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ group=com.uber.sdk groupId=com.uber.sdk artifactId=rides-android githubDownloadPrefix=https://github.com/uber/rides-android-sdk/releases/download/ -version=0.6.1 +version=0.6.2-SNAPSHOT From b3ccf2ac202acfd64c8a5344068cb8c2d4e8c0b0 Mon Sep 17 00:00:00 2001 From: Justas Medeisis Date: Wed, 11 Oct 2017 14:14:50 +0300 Subject: [PATCH 006/165] Verify that onLoginError is not called when receiving onActivityResult with an unavailable error that can be automatically resolved. --- .../com/uber/sdk/android/core/auth/LoginManagerTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 98f15a97..8a52b802 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -62,6 +62,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -278,6 +279,8 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAut loginManager.setRedirectForAuthorizationCode(true); loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE_LOGIN_DEFAULT)); @@ -313,6 +316,8 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImp loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE_LOGIN_DEFAULT)); From aaf3c73a3cc9f90deeb6dbfbe7571b391dc020db Mon Sep 17 00:00:00 2001 From: Justas Medeisis Date: Wed, 11 Oct 2017 14:15:53 +0300 Subject: [PATCH 007/165] Fix onLoginError being uncorrectly called when automatically falling back to the authorization code flow. --- .../main/java/com/uber/sdk/android/core/auth/LoginManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 79c700d3..b70afa2c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -314,6 +314,7 @@ private void handleResultCancelled( } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && redirectForAuthorizationCode) { loginForAuthorizationCode(activity); + return; } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { AppProtocol appProtocol = new AppProtocol(); String appSignature = appProtocol.getAppSignature(activity); From 56654a86cc702a6daf257da86b131fad7c28e1a1 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 9 Nov 2017 20:42:17 -0800 Subject: [PATCH 008/165] Prerequisites for 0.7 release - Updating Travis to build properly - Migrating duplicate Gradle files into shared space - Updating dependencies for Java SDKs - Moving to Github first development model --- .buildscript/deploy_snapshot.sh | 26 ++ .travis.yml | 45 +++- CHANGELOG.md | 8 +- README.md | 13 +- build.gradle | 222 ++---------------- core-android/build.gradle | 194 ++++----------- ...oguard.txt => consumer-proguard-rules.txt} | 0 core-android/gradle.properties | 22 +- .../com/uber/sdk/android/core/UberButton.java | 5 +- .../com/uber/sdk/android/core/UberSdk.java | 4 +- .../sdk/android/core/auth/LoginActivity.java | 2 +- .../sdk/android/core/auth/LoginButton.java | 2 +- .../sdk/android/core/auth/LoginManager.java | 14 +- .../sdk/android/core/RobolectricTestBase.java | 0 .../uber/sdk/android/core/UberSdkTest.java | 2 +- .../android/core/auth/LoginActivityTest.java | 2 +- .../android/core/auth/LoginButtonTest.java | 2 +- .../android/core/auth/LoginManagerTest.java | 8 +- .../core/auth/OAuthWebViewClientTest.java | 2 +- .../android/core/auth/SsoDeeplinkTest.java | 3 +- gradle.properties | 24 +- gradle/dependencies.gradle | 74 ++++++ gradle/github-release.gradle | 206 ++++++++++++++++ gradle/gradle-mvn-push.gradle | 218 +++++++++++++++++ gradle/verification.gradle | 31 +++ gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 54727 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 78 +++--- gradlew.bat | 14 +- rides-android/build.gradle | 199 ++++------------ ...oguard.txt => consumer-proguard-rules.txt} | 0 rides-android/gradle.properties | 22 +- .../sdk/android/rides/RequestDeeplink.java | 5 +- .../rides/RequestDeeplinkBehavior.java | 2 +- .../android/rides/RideRequestActivity.java | 7 +- .../rides/RideRequestActivityBehavior.java | 2 +- .../sdk/android/rides/RideRequestButton.java | 7 +- .../sdk/android/rides/RideRequestView.java | 7 +- .../internal/RideRequestButtonController.java | 4 +- .../android/rides/RequestDeeplinkTest.java | 5 +- .../RideRequestActivityBehaviorTest.java | 2 +- .../rides/RideRequestActivityTest.java | 6 +- .../android/rides/RideRequestViewTest.java | 10 +- .../com/uber/sdk/android/rides/TestUtils.java | 4 +- .../RideRequestButtonControllerTest.java | 2 +- samples/build.gradle | 36 --- samples/login-sample/build.gradle | 53 +++-- .../android/samples/LoginSampleActivity.java | 4 +- .../src/main/res/values/strings.xml | 5 +- samples/request-button-sample/build.gradle | 51 ++-- .../android/rides/samples/SampleActivity.java | 4 +- .../src/main/res/values/strings.xml | 4 +- settings.gradle | 1 - .../uber/sdk/android/core/SdkPreferences.java | 80 ------- 54 files changed, 951 insertions(+), 795 deletions(-) create mode 100644 .buildscript/deploy_snapshot.sh rename core-android/{proguard.txt => consumer-proguard-rules.txt} (100%) rename {test-shared => core-android/src}/test/java/com/uber/sdk/android/core/RobolectricTestBase.java (100%) create mode 100644 gradle/dependencies.gradle create mode 100644 gradle/github-release.gradle create mode 100644 gradle/gradle-mvn-push.gradle create mode 100644 gradle/verification.gradle rename rides-android/{proguard.txt => consumer-proguard-rules.txt} (100%) delete mode 100644 samples/build.gradle delete mode 100644 test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java diff --git a/.buildscript/deploy_snapshot.sh b/.buildscript/deploy_snapshot.sh new file mode 100644 index 00000000..ca650039 --- /dev/null +++ b/.buildscript/deploy_snapshot.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. +# +# Adapted from https://coderwall.com/p/9b_lfq and +# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ + +SLUG="uber/rides-android-sdk" +JDK="oraclejdk8" +BRANCH="master" + +set -e + +if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then + echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." +elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then + echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." +elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo "Skipping snapshot deployment: was pull request." +elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then + echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." +else + echo "Deploying snapshot..." + ./gradlew clean uploadArchives + echo "Snapshot deployed!" +fi \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 738fa9db..4024c3b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,48 @@ language: android android: components: - - platform-tools + # Update tools and then platform-tools explicitly so lint gets an updated database. Can be removed once 3.0 is out. - tools - - build-tools-23.0.1 - - android-23 - - extra-google-m2repository - - extra-android-m2repository + - platform-tools jdk: - oraclejdk8 +before_install: + # Install SDK license so Android Gradle plugin can install deps. + - mkdir "$ANDROID_HOME/licenses" || true + - echo "$LICENSES_HASH" > "$ANDROID_HOME/licenses/android-sdk-license" + - echo "$LICENSES_HASH_TWO" >> "$ANDROID_HOME/licenses/android-sdk-license" + # Install the rest of tools (e.g., avdmanager) + - sdkmanager tools + # Install the system image + - sdkmanager "system-images;android-18;default;armeabi-v7a" + # Create and start emulator for the script. Meant to race the install task. + - echo no | avdmanager create avd --force -n test -k "system-images;android-18;default;armeabi-v7a" + - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window > /dev/null 2>&1 & + +install: ./gradlew clean assemble assembleAndroidTest --stacktrace + +before_script: + - android-wait-for-emulator + - adb shell input keyevent 82 + +script: + - ./gradlew check connectedCheck --stacktrace + +after_success: + # - .buildscript/deploy_snapshot.sh + + +branches: + except: + - gh-pages + +notifications: + email: false + +sudo: false + +cache: + directories: + - $HOME/.gradle \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 86ccf4e8..83b54074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ -v0.6.2 - TBD +v0.7.0 - TBD ------------ +### Fixed +- Moved all release, dependencies, and snapshot code to gradle folder in root + +### Breaking +- Upgraded dependencies on uber java sdk to [modularized SDK](https://github.com/uber/rides-java-sdk/blob/master/CHANGELOG.md). This moved imports to follow the new format. + v0.6.1 - 4/5/2017 ------------------- ### Changed diff --git a/README.md b/README.md index 392a5aab..04431734 100644 --- a/README.md +++ b/README.md @@ -24,17 +24,6 @@ dependencies { } ``` -### Maven - -In the `pom.xml` file: -```xml - - com.uber.sdk - rides-android - 0.6.1 - -``` - ## SDK Configuration In order for the SDK to function correctly, you need to add some information about your app. In your application, create a `SessionConfiguration` to use with the various components of the library. If you prefer the set it and forget it model, use the `UberSdk` class to initialize with a default `SessionConfiguration`. @@ -319,7 +308,7 @@ service.getUserProfile().enqueue(new Callback() { ## Sample Apps -Sample apps 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.6.1). +Sample apps 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). The Sample apps require configuration parameters to interact with the Uber API, these include the client id, redirect uri, and server token. They are provided on the [Uber developer dashboard](https://developer.uber.com/dashboard). diff --git a/build.gradle b/build.gradle index b19e2fcd..d9a02fe5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,208 +1,36 @@ -apply plugin: 'distribution' -apply plugin: 'net.researchgate.release' -apply plugin: 'co.riiid.gradle' - -import groovy.text.GStringTemplateEngine -import org.codehaus.groovy.runtime.DateGroovyMethods +/* + * Copyright (C) 2017. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' - classpath 'net.researchgate:gradle-release:2.3.5' - classpath 'co.riiid:gradle-github-plugin:0.4.2' - classpath 'net.saliman:gradle-cobertura-plugin:2.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")) + apply from: 'gradle/dependencies.gradle' repositories { jcenter() + maven { url "https://plugins.gradle.org/m2/" } } - - 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 - } -} - -task updateChangelog() << { - def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') - def changelog = rootProject.file('CHANGELOG.md') - def changelogText = changelog.text - if (!changelogText.startsWith("v${newVersion} -")) { - def updatedChangelog = "v${newVersion} - TBD\n" - def dashesCount = updatedChangelog.length()-1 - updatedChangelog += "-"*dashesCount + "\n\n" - - changelog.write(updatedChangelog + changelogText) + dependencies { + classpath deps.build.gradlePlugins.github + classpath deps.build.gradlePlugins.release } } -afterReleaseBuild.dependsOn(":core-android:uploadArchives", ":rides-android:uploadArchives") -updateVersion.dependsOn ":githubRelease" -commitNewVersion.dependsOn ':updateChangelog' -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" - } +task wrapper(type: Wrapper) { + gradleVersion = '4.2.1' + distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.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/' - include 'config' - include 'test-shared/' - include '.travis.yml' - } - - from(rootDir) { - include 'README.md' - filter { String line -> - line.replaceAll("_version_", unsnapshottedVersion) - } - } - - from('core-android') { - filesNotMatching("**/*.png") { - filter { String line -> - line.replaceAll("_version_", unsnapshottedVersion) - } - } - exclude 'build' - exclude '*.iml' - into 'core-android' - } - - from('rides-android') { - filesNotMatching("**/*.png") { - filter { String line -> - line.replaceAll("_version_", unsnapshottedVersion) - } - } - exclude 'build' - exclude '*.iml' - into 'rides-android' - } - - from('samples') { - exclude '**/build' - exclude '**/*.iml' - into 'samples' - } - } - } -} +apply from: 'gradle/github-release.gradle' +apply from: 'gradle/verification.gradle' \ No newline at end of file diff --git a/core-android/build.gradle b/core-android/build.gradle index 2f2d8935..7932f16a 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -1,176 +1,62 @@ -apply plugin: 'com.android.library' -apply plugin: 'maven' -apply plugin: 'signing' -apply plugin: 'com.github.dcendents.android-maven' -apply plugin: 'cobertura' +/* + * Copyright (C) 2017. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ buildscript { repositories { jcenter() + maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath deps.build.gradlePlugins.android } } +apply plugin: 'com.android.library' + android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion deps.build.compileSdkVersion + buildToolsVersion deps.build.buildToolsVersion defaultConfig { - minSdkVersion 14 - versionName version - consumerProguardFiles 'proguard.txt' + minSdkVersion deps.build.minSdkVersion + targetSdkVersion deps.build.targetSdkVersion + versionName VERSION_NAME + consumerProguardFiles 'consumer-proguard-rules.txt' + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } - sourceSets { - test { - java.srcDirs += '../test-shared/test/java/' - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 } } -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)) - classpath += configurations.compile -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -artifacts { - archives sourcesJar - archives javadocJar -} - -signing { - sign configurations.archives -} - -project.archivesBaseName = artifactId - - -def getReleaseRepositoryUrl() { - return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" -} - -def getSnapshotRepositoryUrl() { - return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL - : "https://oss.sonatype.org/content/repositories/snapshots/" -} - -def getRepositoryUsername() { - return hasProperty('ossrhUsername') ? ossrhUsername : "" -} - -def getRepositoryPassword() { - return hasProperty('ossrhPassword') ? ossrhPassword : "" -} - -uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - repository(url: getReleaseRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - - snapshotRepository(url: getSnapshotRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - - 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' - } - - developer { - id 'tyvsmith' - name 'Ty Smith' - email 'tys@uber.com' - } - } - } - } - } -} - -cobertura { - coverageFormats = ['html', 'xml'] - coverageIgnoreTrivial = true - coverageExcludes += [ - '.*android\\.support\\.v7\\.appcompat\\.R\\.*', - '.*com\\.uber\\.sdk\\.android\\.core\\.R\\.*', - '.*android\\.support\\.v7\\.appcompat\\.R\\$.*\\.*', - '.*com\\.uber\\.sdk\\.android\\.core\\.R\\$.*\\.*', - '.*BuildConfig.*'] -} - dependencies { - compile ('com.uber.sdk:rides:0.6.0') { - //Do not need Google OAuth based logic in Android - exclude group: 'org.slf4j', module: 'slf4j-log4j12' - exclude group: 'com.google.oauth-client', module: 'google-oauth-client' - exclude group: 'com.google.http-client', module: 'google-http-client-jackson2' + compile (deps.uber.uberCore) { + exclude module: 'slf4j-log4j12' } + compile deps.misc.jsr305 + compile deps.support.appCompat + compile deps.support.annotations - compile 'com.android.support:appcompat-v7:23.0.1' - - //Needed for runtime annotation preservation in Moshi - //Will require Java SDK updates to remove safely - compile 'com.google.code.findbugs:jsr305:1.3.9' - - testCompile 'junit:junit:4.12' - testCompile 'com.google.guava:guava:18.0' - testCompile 'com.google.http-client:google-http-client-jackson2:1.19.0' - testCompile 'org.mockito:mockito-core:1.10.19' - testCompile 'org.assertj:assertj-core:1.7.1' - testCompile 'org.robolectric:robolectric:3.2.2' - - //Needed for Cobertura checks because of above exclude - testCompile 'com.google.http-client:google-http-client-jackson2:1.19.0' - testCompile 'com.google.oauth-client:google-oauth-client:1.19.0' - testCompile 'org.slf4j:slf4j-log4j12:1.7.5' - - cobertura 'com.google.android:android:4.1.1.4' + testCompile deps.test.junit + testCompile deps.test.assertj + testCompile deps.test.mockito + testCompile deps.test.robolectric } + +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/core-android/proguard.txt b/core-android/consumer-proguard-rules.txt similarity index 100% rename from core-android/proguard.txt rename to core-android/consumer-proguard-rules.txt diff --git a/core-android/gradle.properties b/core-android/gradle.properties index 9b06d502..7291c359 100644 --- a/core-android/gradle.properties +++ b/core-android/gradle.properties @@ -1 +1,21 @@ -artifactId=core-android + +# +# Copyright (C) 2017. Uber Technologies +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +POM_NAME=Uber Core SDK (Android) +POM_ARTIFACT_ID=core-android +POM_PACKAGING=aar +POM_DESCRIPTION=The official Uber Core Android SDK. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java b/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java index c1caa787..9626bb0d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java @@ -35,14 +35,15 @@ import android.support.annotation.StringRes; import android.support.annotation.StyleRes; import android.support.annotation.VisibleForTesting; +import android.support.v7.widget.AppCompatButton; import android.util.AttributeSet; 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 { +public class UberButton extends AppCompatButton { private static final @StyleRes int[] STYLES = {R.style.UberButton, R.style.UberButton_White}; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java b/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java index 5cb66cf3..907319dc 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java @@ -24,9 +24,9 @@ import android.support.annotation.NonNull; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; -import static com.uber.sdk.rides.client.utils.Preconditions.checkNotNull; +import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; /** * Uber SDK management class. Uber SDK Classes behavior is undefined if the SDK is not initialized. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 3b78bfd9..a4ee7d98 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -37,7 +37,7 @@ import android.webkit.WebViewClient; import com.uber.sdk.android.core.R; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import java.util.Locale; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java index f7470390..e5097144 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java @@ -39,7 +39,7 @@ import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.UberStyle; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import java.util.Collection; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 79c700d3..52290f95 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -28,17 +28,18 @@ import android.support.annotation.Nullable; import android.util.Log; +import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.AccessTokenSession; -import com.uber.sdk.rides.client.ServerTokenSession; -import com.uber.sdk.rides.client.Session; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.AccessTokenSession; +import com.uber.sdk.core.client.ServerTokenSession; +import com.uber.sdk.core.client.Session; +import com.uber.sdk.core.client.SessionConfiguration; -import static com.uber.sdk.rides.client.utils.Preconditions.checkNotEmpty; +import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; /** * Manages user login via OAuth 2.0 Implicit Grant. Be sure to call @@ -84,7 +85,8 @@ public class LoginManager { static final int REQUEST_CODE_LOGIN_DEFAULT = 1001; - private static final String USER_AGENT = "core-android-v0.6.1-login_manager"; + private static final String USER_AGENT = String.format("core-android-v%s-login_manager", + BuildConfig.VERSION_NAME); private final AccessTokenManager accessTokenManager; private final LoginCallback callback; diff --git a/test-shared/test/java/com/uber/sdk/android/core/RobolectricTestBase.java b/core-android/src/test/java/com/uber/sdk/android/core/RobolectricTestBase.java similarity index 100% rename from test-shared/test/java/com/uber/sdk/android/core/RobolectricTestBase.java rename to core-android/src/test/java/com/uber/sdk/android/core/RobolectricTestBase.java diff --git a/core-android/src/test/java/com/uber/sdk/android/core/UberSdkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/UberSdkTest.java index f10d8e02..25314879 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/UberSdkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/UberSdkTest.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.core; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index 37f0dc9a..c98feecb 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -29,7 +29,7 @@ import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index bc914417..8fb22202 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -32,7 +32,7 @@ import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 98f15a97..44f994a1 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -36,8 +36,8 @@ import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenAuthenticator; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.Session; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.Session; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; @@ -95,7 +95,9 @@ public class LoginManagerTest extends RobolectricTestBase { "uber://connect?client_id=Client1234&scope=profile%20request_receipt&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; - private static final String INSTALL = "https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v0.6.1-login_manager"; + private static final String INSTALL = + String.format("https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v%s-login_manager", + BuildConfig.VERSION_NAME); private static final String AUTHORIZATION_CODE = "Auth123Code"; @Mock diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java index 0d72ddd8..47f7b107 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java @@ -29,7 +29,7 @@ import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 78782dae..60cb8137 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -63,7 +63,8 @@ public class SsoDeeplinkTest extends RobolectricTestBase { private static final int REQUEST_CODE = 1234; private static final String DEFAULT_REGION = - "uber://connect?client_id=MYCLIENTID&scope=profile%20history&sdk=android&sdk_version=" + BuildConfig.VERSION_NAME; + "uber://connect?client_id=MYCLIENTID&scope=profile%20history&sdk=android&sdk_version=" + + BuildConfig.VERSION_NAME; @Mock PackageManager packageManager; diff --git a/gradle.properties b/gradle.properties index cff12a48..1ffacfba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,19 @@ -group=com.uber.sdk -groupId=com.uber.sdk -artifactId=rides-android -githubDownloadPrefix=https://github.com/uber/rides-android-sdk/releases/download/ -version=0.6.2-SNAPSHOT +GROUP=com.uber.sdk +VERSION_NAME=0.7.0-SNAPSHOT +POM_URL=https://developer.uber.com + +POM_SCM_URL=https://github.com/uber/rides-android-sdk/ +POM_SCM_CONNECTION=scm:git:git://github.com/uber/rides-android-sdk.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/uber/rides-android-sdk.git + +POM_LICENCE_NAME=MIT License +POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php +POM_LICENCE_DIST=repo + +POM_DEVELOPER_ID=tyvsmith +POM_DEVELOPER_NAME=Ty Smith + +GITHUB_OWNER=uber +GITHUB_REPO=rides-android-sdk +GITHUB_DOWNLOAD_PREFIX=https://github.com/uber/rides-android-sdk/releases/download/ +GITHUB_BRANCH=master diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle new file mode 100644 index 00000000..166932f5 --- /dev/null +++ b/gradle/dependencies.gradle @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +def versions = [ + androidTest: '0.5', + support: '26.1.0', + uberJava: '0.7.0', +] + +def build = [ + buildToolsVersion: '26.0.2', + compileSdkVersion: 26, + ci: 'true' == System.getenv('CI'), + minSdkVersion: 14, + targetSdkVersion: 26, + + repositories: [ + plugins: 'https://plugins.gradle.org/m2/' + ], + + gradlePlugins: [ + android: 'com.android.tools.build:gradle:2.3.0', + release: 'net.researchgate:gradle-release:2.3.5', + github: 'co.riiid:gradle-github-plugin:0.4.2', + cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1', + ] +] + +def misc = [ + jsr305: 'com.google.code.findbugs:jsr305:3.0.2', +] + +def support = [ + annotations: "com.android.support:support-annotations:${versions.support}", + appCompat: "com.android.support:appcompat-v7:${versions.support}", +] + +def test = [ + androidRunner: "com.android.support.test:runner:${versions.androidTest}", + androidRules: "com.android.support.test:rules:${versions.androidTest}", + junit: 'junit:junit:4.12', + robolectric: 'org.robolectric:robolectric:3.2.2', + assertj: 'org.assertj:assertj-core:1.7.1', + mockito: 'org.mockito:mockito-core:1.10.19', + guava: 'com.google.guava:guava:23.4-android', + wiremock: 'com.github.tomakehurst:wiremock:2.10.1' +] + +def uber = [ + uberCore: "com.uber.sdk:uber-core:${versions.uberJava}", + uberRides: "com.uber.sdk:uber-rides:${versions.uberJava}", +] + +ext.deps = [ + "build": build, + "misc": misc, + "support": support, + "test": test, + "versions": versions, + "uber": uber +] diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle new file mode 100644 index 00000000..662f1dbf --- /dev/null +++ b/gradle/github-release.gradle @@ -0,0 +1,206 @@ +import groovy.text.GStringTemplateEngine +import org.codehaus.groovy.runtime.DateGroovyMethods + +apply plugin: 'distribution' +apply plugin: 'net.researchgate.release' +apply plugin: 'co.riiid.gradle' + +ext.set("unsnapshottedVersion", VERSION_NAME.replaceAll("-SNAPSHOT", "")) +ext.set("samples", project(":samples").subprojects.collect { it.path }) +ext.set("isReleaseVersion", !VERSION_NAME.endsWith("SNAPSHOT")) + +["githubToken", "ossrhUsername", "ossrhPassword", + "signing.keyId", "signing.password", "signing.secretKeyRingFile",].each { + checkAndDefaultProperty(it) +} + +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 : GITHUB_DOWNLOAD_PREFIX + "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 -> + println task.path + 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 + } +} + +task updateChangelog() << { + def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') + def changelog = rootProject.file('CHANGELOG.md') + def changelogText = changelog.text + if (!changelogText.startsWith("v${newVersion} -")) { + def updatedChangelog = "v${newVersion} - TBD\n" + def dashesCount = updatedChangelog.length()-1 + updatedChangelog += "-"*dashesCount + "\n\n" + + changelog.write(updatedChangelog + changelogText) + } +} + +afterReleaseBuild.dependsOn(":core-android:uploadArchives", ":rides-android:uploadArchives") +updateVersion.dependsOn ":githubRelease" +commitNewVersion.dependsOn ':updateChangelog' +githubRelease.dependsOn project(":samples").subprojects.collect { it.path + ":githubReleaseZip" } + +release { + failOnCommitNeeded = false + failOnPublishNeeded = false + failOnSnapshotDependencies = false + revertOnFail = true + tagTemplate = "v${unsnapshottedVersion}" +} + +github { + owner = GITHUB_OWNER + repo = GITHUB_REPO + token = "${githubToken}" + tagName = "v${unsnapshottedVersion}" + targetCommitish = GITHUB_BRANCH + 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/' + include 'config' + include 'test-shared/' + include '.travis.yml' + include 'README.md' + include '.buildscript/' + } + + from('core-android') { + exclude 'build' + exclude '*.iml' + into 'core-android' + } + + from('rides-android') { + exclude 'build' + exclude '*.iml' + into 'rides-android' + } + + from('samples') { + exclude '**/build' + exclude '**/*.iml' + into 'samples' + } + } + } +} + +subprojects { + configure(subprojects.findAll {it.parent.name == 'samples'}) { + task githubReleaseZip(type: Zip) << { + version = "v${unsnapshottedVersion}" + + from('.') { + filesNotMatching("**/*.png") { + filter { String line -> + line.replaceAll("compile project\\(':rides-android'\\)", + "compile '${groupId}:rides-android:${unsnapshottedVersion}'") + } + filter { String line -> + line.replaceAll("compile project\\(':core-android'\\)", + "compile '${groupId}:core-android:${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/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle new file mode 100644 index 00000000..5fe4770b --- /dev/null +++ b/gradle/gradle-mvn-push.gradle @@ -0,0 +1,218 @@ +/* + * Copyright 2013 Chris Banes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'maven' +apply plugin: 'signing' + +version = VERSION_NAME +group = GROUP + +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + +def getReleaseRepositoryUrl() { + return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL + : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} + +def getSnapshotRepositoryUrl() { + return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : "https://oss.sonatype.org/content/repositories/snapshots/" +} + +def getRepositoryUsername() { + return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" +} + +def getRepositoryPassword() { + return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" +} + +afterEvaluate { project -> + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + repository(url: getReleaseRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + snapshotRepository(url: getSnapshotRepositoryUrl()) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + } + } + } + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + + if (project.getPlugins().hasPlugin('com.android.application') || + project.getPlugins().hasPlugin('com.android.library')) { + task install(type: Upload, dependsOn: assemble) { + repositories.mavenInstaller { + configuration = configurations.archives + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + } + } + + task androidJavadocs(type: Javadoc) { + source = android.sourceSets.main.java.source + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + } + + task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { + classifier = 'javadoc' + from androidJavadocs.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.source + } + } else { + install { + repositories.mavenInstaller { + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + } + } + + task sourcesJar(type: Jar, dependsOn:classes) { + classifier = 'sources' + from sourceSets.main.allSource + } + + task javadocJar(type: Jar, dependsOn:javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir + } + } + + if (JavaVersion.current().isJava8Compatible()) { + allprojects { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + } + } + } + + artifacts { + if (project.getPlugins().hasPlugin('com.android.application') || + project.getPlugins().hasPlugin('com.android.library')) { + archives androidSourcesJar + archives androidJavadocsJar + } else { + archives sourcesJar + archives javadocJar + } + } +} \ No newline at end of file diff --git a/gradle/verification.gradle b/gradle/verification.gradle new file mode 100644 index 00000000..f3c2db78 --- /dev/null +++ b/gradle/verification.gradle @@ -0,0 +1,31 @@ +subprojects { + buildscript { + repositories { + jcenter() + } + } + + repositories { + jcenter() + maven { + url 'https://maven.google.com' + } + } + + apply plugin: 'checkstyle' + + 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") + } + + afterEvaluate { + tasks.withType(Checkstyle) { + configProperties = ['proj.module.dir' : projectDir.absolutePath, + 'checkstyle.cache.file': './build/cache/checkstyle-cache'] + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..27768f1bbac3ce2d055b20d521f12da78d331e8e 100644 GIT binary patch delta 48668 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 bZs2|cAtp4)c|c1#L7#V^dD+{ZU*G-@TU7kz delta 43945 zcmZ5{bC732lWkAiwr$(CZQHhf-92sFwr$(CIX!LLZ}z>7*!OKk+^VR!ReAr(xOp$pZlaStbZ7$P!zzEwM2(va&P& z5>_E0Q9-c-A>2PcJO&2(9}_VDO#C|!1pHsqzXAI1ad5U^0Qp~M5~hiu|KDf+>qf#0 z)&I9kG6mnn_@9^1-LnDc|Li&bvn8731Wtgc;^pFMW~b=v;ArOTYGvkCDEuS(n5@KrjG7%UiKLyZmxr<1Rcmqttg<8Zq4koZI;JCyvai=zkx z5KMBys6r(gAAU8k@Hp&FZ*BAt2=w_wD2|$-SfR>lQetY#9F#`5*%sGl3^uwuWULR` zp*V4Zx58~3N%B7cD_k)T=N$e5h!mo?f(|L~zzkpPDv6A9bRVb;*M`Ym6F8ooeQ~BM z;xQO&?h%Me<6ceF(-hx}itgU!Z>r!SQPxeUQDRkV2oajjoG6ioKXl_ywr-_%AH%dU zc%_wJN7WO&5;@q-GH4oU+QYO+9KRRX${FfoGTL&EyhD5^bOkD2UlvCK0=r-d-)1|s zC(-!Q*YDs(jO2-bcMv?As&qd-dNNxmO35_D4-l`UlSCOc6sfi?uSodgKw0hQh9K*9 z{ub!R(jeghpG3bt3cS9Z?SPmnqxv^Td>#sjv zrch3P4011d^$0(hh#m$2ip|SRNQFaM;M3HoxFRGuM~1LP{Kcb&GJCI6Tw){G-lu3! z8Di6W)x1Sznq4LqqXq|`p^Wn;B8X&coH2dlqMEgfK-b%a*#mL!O`KB-v8MncpE)_4 zBSAUMF)GEq`-4!^3M$9gD986j&YA^t}A$> zTIIhUK@(C#dxR_(s~Mu5A2CdTyfIH0F$0LmL`E2mz~Z6yr2cE<6gF`>k39BY}t|)E?>VNtVq&U!dk?4=CK#8=(6n8`Q z@~NAZ8#ZuWA-AG7rC+Tx9Z6EhN(x~aX;OyS0=#yJJtR8b^p zg$b}!hJ0E?`!;8H?!5xIs3f~CR&_-%oq@0QLc&lw;K^xQR8yh*>dcS)=VR9*jUqkx zA!u<&KyJ>SeK7=Dw6qXfuJSEwF4Vpj*xZ3v;7Zh!N`61S(k-w)>^>P7!x0+T#{y&cd-=ZJdi4!3piZ!|7o-0Jnm?YJ?d7po>bV0HnKF6; zJnhkDR!J?9?1$P|&4oZCfST?ckwIg)5*^#wA8YTDtaAPH;tlk+GNv)1#)()}99&^a$3_J!kVe)TPtf;Cf|3k4ER2hE^u4>F>M9BV-a)&_ z6*tvXT3vCckbA=&V9g>5A?4D#HroKv^PXbEVP(HU zT%jvwNY@dTGpOk;Nn5oA!h}eXh{xP(L`)t0P&e`clBZ3go<=#hrh^1`dxCFckYb+( z1-{DSaj#vR^+0}Uh&x8+^%14MER#-rj9W+&4n}um184O; zL6Y9lU%p|#qxT~7=EXs!J_oxDyk2l_zV~J34F&P0N;iPW6- z*>axrHGA28qTJuh1~aE!lq5K%>q9&CmkE~#^nu)srN{u34SaLW-vSX13|N{`dv}!h zB1jEEzL|@A5?{qr>tj-$->ZKn{37>&)3*2Wi{G&efz$S7^h(zyMv&2$NxZo9fXAq= z3O`vLJ1$G+#10smvN%;g=Nn4ZH zfSANB`z1ldP>Uo^c|4BU0y?@A!Gc(wbMST`CADOB++sl`VV#JqNvY@y>h*-HfMJzD z(cob)gnkhs^ELS_TAT=&x#`(ifd{Y4J%Il=Z~!;2qF|sld^A`3?2%H3r-sr+CTK)DH?ku*EI~LzHEIoyY?Bk<6 zbFawtg;x`;jD8NH%*i25dfW()W=`SC=sTn{1|{^=uBcp!n`UHHB0TFPps5x#p%zAP zk$1cj@eg<`VnDGMA|6`{7UARX`2jVe@ZsOKkV5tmLZtYnFlLY|&_Yh3|F;X#7@I5*J%~o1XnkJ=}RFO8QQ_dkN#FWF9 zy%`NH?^XP35H@yb&EL5c-UQlRy!S(*qOS|ePM>h*aocC^BKLvkBzL3l=k0+3DD18& z)IGY~Q$z$$oHcv2Jd%U$mFyuJ9L?mOk=_G^np{n`0^lh#L>>+5q+ZJ;H7n(;|2y`K zklxZ0hh_27m}GaLL|k{YE3un`frp{w#)grRMoM=GH&`{-K@?aDtu&Gb8W^P1tn)YY z`4HX`w6Dl>wqm{6bn~z%Js(ZB%cyN+b}18&eQ_z=Sn>(+(h2!Yb~b>7%h0xRiZ|AmM*-!l(GJ=+uP}%TVq{Q-KAE_sy^yeQ~F$1GtCId z8Ce>kgiQ8rPi4-F*`6%C1jMNBJ(LMdSc(K07{C>J-kV+}d2A;tBZ%B@TiT3h07TYs^ppzz=u6Wh6++v!2H73@Cc4dBNbeIq0R5G_}8334FKh@%42jBZ_ox+ui|5EukvFruRl_Y~+#e{hIY+p*@NuivgR9c`PEt@b2jo4WIAn6; zp9tRyUuzGPzh-gzpKL|J#%$6Bk^mx?M|F7jb?}kd3dInfk&bHZ?>rg>pVtuxiQv3{ z@o|Y)V4xw3e%pX>FNo1$)4Uvr#CHpm9sqJW}jY9 z6b1flGC%s~Z3p|tr7o~Hfcu%lWtI1ISjph)oEhuj%kfpMf0tX0fv_sLo&Xvih_q23 zKZJY3)=T#3%!BmOlOF6n^40AnF9U6@0ymwAll72#1JJ0b|=nu$${tRj4?V!N0A%K9Kl9=Qe0X{eyXnZCz*k-&^ zxFcGZ+(HdW;;FV(Rw$J6c=dE->J7GQI_IG0u{9CA6|6_IiR+4#dcbC7W^kaCAMo_e z%HdWZpW_5z+dM3(rf#xZ3VFNiFFQWF9Va^(fS;3H9-!HM1LWAkb_k0GYM~*w2=O^b zmBUh6;S;mp6gI@UfE2=laT@ZtYD(K{JSbxO+j}|tRWVyh&Ts{pS6Bg_sFT9i>hL)! zPyD>NLog_YL$ciN=^`EC-u^EPGTbqmZC|P3G%;VY$U5oMk+s5CZXCexEQiK94X;OS z)V;&+EhS%y$Q$Wb{N6cEdx^W>l>Xy94pHqS`~ATUg~`mB0JE&kMiZM*dcoPVPV3kd z)G1}UUlQ~)(Wc~$G7n7~86|fWzV24;_T(I1Qi8UJoz5asX{bHvxnvODcijO!CZYBl zk@gIfWD;og4&LVHcbDtBc5A(b6{ynZ-6fev;u&M@I3n8=E|Z3EbiD(r&m4902WO_-EkD{32?%8Xp~ z%M~PUwG3K0Xjj4p7FLNZ1J$b>Us|#tU%h8{a*91MPM6ljQ!&B( zto{Jt!l}p-d1_hHW(zU%a2yKxP4 zeE5*>K{kgMBcbB{caPahV{&2ULS&^G4Ch}gt@B6|>t}K9z1Q^1p(z}8nC9d2a-hIi zukryKcRw+`2dYR-d&tR}iqse!Yxf;>w<$C3;x14q(eQYZ&CfqR%}=l6ZY%P0bAQ~q z*jTt70IAm8yRGCWHpyObF-p>jSpm5_8|4N8h$qSHv)Hj6h4;h}eqqtzUSZKjy75l< zaS^Wx(X{@wM=GAuqr|!KcTlp6diIaBM--lVqs;23>KsLwZq3R7yvgfKC*h+x6V%EsMnn6y(0wI zmxl}&;%~|$h1aHNxo)B(hS#b9cn_J3!4ioUWBIlf;v)YfxX?Rvl3GYk%zV&adQU6= zQs|b(&D(mPX-DahcKh`^HDr6;r8QihYblF(KaB_^?Cp^~)9sfq`y=t;uR%e2*L$Xa7QwM_{?R z7v@()m=S3`>kaLL5E3>_@3!O&yp(+rAX=A27CAd04m|tuT(l|l_r);CPg%NG2*pV+ zhsJ5Ng}a68nl=aj-G8GsND`WedC#Q?|D)0Qu|W zEU%qrtM;>GHalEmKu-fqJXm(VLT%Bb*IJ~!FKAp)qjVE`u_ga9BSF4^nmV?GRSYy; z3qjEa$ui7xSdto~LwhMfnR+39IEm;laZ6Gkam*3Sr%S>ko_5JpTbhR7rss6c%RQGQ zn)z`}i1Nk=@*TPGKC8VH_)N6mfCMsw+_@iR(u8-N301Hfwcz`SZ|cFMi9l7fgo1hW zVKINy8sjT6ZCrD27va_6>P$Lc*vDm(S9gdE#rPeo^}ku6#a}u}hAk3{GD6N-Yo<;?su$a=b=xD^kR*Z%E30r>sZSz`R#qHgoOg|^|O7Nc% zbia*~;T|3cD2EUTi11%=9X&~w4IZEg<%@da22k*swq}ELC8GPQSr6JtW>3r5N*|3Z zH6A_$^NV*ec6fg$=joDGayS-c45~M=nbj_6iM`@N)$M|PfGN>#WL-|1LvD-JZjr?5 zqS-J#qVQ#u#ot#!y6Xhoa` zgr{A=8W<-oXx&pucc4Rvx-P*!_!$Ruo7^HR^)_lTl6`7vNwiLfTqeLhgeS-~-9J2( z$Lz>Fq>ZwhO>)fCA-HWK@M=EG1JsM+CUH(rs5hqTk4sy$J5_fW)$&?j-N)^Ifr=kU zQ5Zb@BQ8Iyqv7kaNVM7TRJ0x1!F6|{x%k{ z!oMA&)60djHV|?)jV6G_#X_c@qwEs0q|Ozl`82JxBdHnwogC@2yM}aoCOb-ZLs9?& ze9dCnsL5Hfllq>;@ZF#J7`pak|8=leq zbEZ;`)mwhTeAv<*(++pBkQT~;U4EX~~cm({KweMyD?r5|f%(CC|vrY@(u) zyAhWq@g5EZ7zE(099x3g`@6zgx$PkWk5z8HwW%R~T6vs-$;z%*>-}-L-HcQZtw(7n z$eLywMKjI~)|g?onl^aQ)%h3vftb$=s`FMh}Zk3igpRT8G z!{csxZa**rV+tl_ax8Wd)Qr5&yRr!_xq)bVXR`f^!WrPmO6L)4p{6}SI%4Km(8zKu zqzvt{*@@303QA{*1j4!YGTclu9^2Bbj2!-jUn$y}?bgPA4AG<@1y^Wu{7E&^R5Ia> z6wbdCnd0ES1jkftE-?%e1fr31BgZYw#hCC{syesT#lk0@5JuC-4Cg7Wi-7lxr>+d) zPX(uuwg|v5SQq37yP`-O_zX$g2RkcPNQi~4dSYU+9Dj*yu!cBwP@F8qeG&c72nJ67 zz^fId#Tk(H%AIk=1`3*Suj^s=uj4f|G51~;U+=-z(xz`m+n6Fe$O3<@g!CYmL@OLx zS2}t|eL`nK2YpQ*+8!mn`xIhVIH>zg-pQ{BS6#r|U-aWo;p|oVVW!$Yb4|jKFIP^N?3|+savm(<< zmCTt%+lhuv+UnE?+{~p-njN)Y5P!uY#wl2ee@3p#8Oftk3M_ZgV%S-bH2EHsJBM3S zyvPpq2vxzNAF4{b#IgRhO1r_kANAhIhuR;#?P_%cgD7vH-@(gWs{R}Z!k4}^sQAiW z>A7O}Q$$yW+(tM~d|Acm*F94Oc(Zk?2XX+o3b}*win2kdCHNzKLmP48YO>OXnZ+?X z72{UEZGn{GFnqWzb5Gz2nra2BBX?WwZmuqePCPOCv7i2REZHjG0146CE;at~B3X)B z8qrV>XxeGV&m5`zdm{d5*>n*5HZ!ZGApMlM`6gj5T`1SUXt z_&Yvn-^EHiFxrlx_$00X9!J6nA5WWu#v5oJDs`D^ZyH<3m@58>Qb-ZCpH`&o)!~EI z$iAC-JW$i2>HN8a9Y`sbDzdy@ zyNgalkBOynE;QBGbr;}`4W0pJOt-Nx+68dK<^FUoQy28!dM<)tu3WjTj(1Hw-UC8| zmIcNq#|DyG`kEt1zr-sAa+{H0zM&`o9enJegJg08kfSy*)Uo&ttwHnieUG*pSFgb|+7pPa zgmRO^B$@=gj_Dv#?Of)0!=ir^ryM2pl~E#qtw`i?Gdkt8V>#kCQDMl5?Ya2BM1*hd zraMFphN;RefB_ver(!eH^$3)XC2#=^HwN>YS@2_s2;k&FU!DQ@6~FF2w)0qoxd8ND z25x_FiuSKO`Sw*YipVgk(y3N!sGXrhk#8l*xOYk1A99*t8=`_7D&(4T3sogV^`R5w zQHEI~>X5nRQk=`i+Lden$j5zfy&tceC?j8ey+$U*@t&@+%_S`IDg;WJS%LYP#Rh4V zS9cskzh&yw#d`xbh^!0rB0xmjf)2bZOTFvu@>`k{l+)q5ipNV?6IhUu*0CZG$uRRu zTuwutEGPy2AGfs4tu_=NV2X_u`M23+rn z&oLyHLD|8isL+^N7pW~Dj5n>7Y(FEfFzOX{dTC=-R6q3!yCErDoJOH$ z7OB-4KcRcxEoB_9M5XaDEMy{2TK;ij)jpQ=L%gEQc7tT+7rt^$_NF?tT)A|fvu3*- zpP4;X63hpHQoJmgJaV4$3EdYTPERMLQzg9UkAn2Y=?m@gDcF#>G}}YMLmCv;oUS6Y z>FpdmUEZ#|T6Zg`wAYNbl}r)b&nI0=k{`!tUDoOHdyifRCQi#(lhUD@o{5;9Q!W!oyCi zREB?`*~EEMdAkKw#5Dr%-M?q`%gRGJUG&eO}}*R5TSQ?)iD z{k8z}GmFn!n@l1ff2}M`bKS*EKkQtV*(AEUJ|CJPAnb3HhrNAB^a9r^*o#$n4}^(O zF>^|I;%0R}^d&P&t`D%Z&z!GD>ZkFZ5!!m>v+g}_bf$4^QalR!U`K3Du`&zr%K*9W z%uddU)(hB-Us&lh;%bmz*rY)2z9(Y!JU zizkf!xw{e!hGSEXqiXF(Tj41;#KOZ3Qq)6l?w~^|jBg_2uz7`f;A1e&q5k0GumM|N z`R`Wk?J&;1q5kmn{^&k=rFmo9Ftevv5PJ^hJKyAft4=;>emVZXNEU8w;;jaSSnvUD z8ST8Gb;uEUlV1#r{SS||THY^)GROP0a4EbZ{5LMSCyiP~iwAd<{STPT<3|CzNwMHG zdeB(A)b~RSTv9Ef{?rqDK^*sBQQHoEV5ZZY7gG@)pueEMsE6q# zY<=N*_&A#q_!ZLd5zSY78$RMZp#JKQ6N6s;d-{soQ#Q4Sdq24x5f1>w%0sU! z%*-=mJeLViRfLf8zaDQOA>&((&zU9o#>8h+Ym5^qQ(&B zHv-E;{(^mS1keCE@@%_OT)F_Ka)Csp*9V4t|61QC>I@R)hmqDWJn66nuPpOnWP#br z74*t1!pLd%VX_&EdQ>K_(FX;rgSpep&ysxk5DpbRLhs+qq=w^HA7N!lRVx_E6D}$q zlrVsY>L|`MkHg=Tm3OzysDP}Dnb&>+8DdHx=1s1sb3F7#Kci9lq^K{|6;S&@75a0* zfFjOBt_M6Za1pZ`jz!7;HIV3&@B4)`g>+NLGr>x87zy-6?nn0f(Ek#UU|qx7mS6qu z0=9C4sGFoQkM>>zD-zP1*$^SzM^^$)3JCM(5NQPIsg~M!Mx!Y2#L*4n{A7FYnLenM zeSFWU)c$P7$@#A4^w+?hf8(~+LgMy9`guP6{N+b-G${O$tV=I_T60B`KEd(LafT2D zH8EmkAR#&ZozV0X7idc%WlAPFy(gh;PQibB^Ygz1L=xNB!-M}AxD=@WO9RXO!UO1R zsUz|YHkzrD1!y5B31$P0m^UmXFM?Cb32(qjG0NaAimhXi5X-2hLb=MMHGjeRUC9_^ zVGL>Xmfn0}_?`Dz@@O;;;JlrObxlv_a9RA_P5AjdX-fmz?T=#yZl^fHE+y?|Fwz#s zF*ltymLKIsf32q+`rRH)M*DEkO8`J<|5HFfgN%#}Zpvz;HkcXhW-IyHA4lr(5RQ=L z>v=gA#>_uBhRlB_ay&(cr4gSPkG`KZ4?B~Kx(_9>3Kp(no(W|cX#=h^Ytfe8QfYri z3N<{FTv=C@I{G43V0mN7^(&#>%FGx`M(%?4d0F}8IUTLbBGY}gbSAM93ksmqVrw1) zbNlyISmxVdh)X3xz zFt*-RkTueKveo)!f9~RnF1>xFmH1%3&!C~ZtsR+u>0ww?@Fo(JB~{c_xTj^Uf$?*! z6xSs!OZ|H5tg`#`N`je$*&zV_MS8-f%AJRaae_~Qpm;sECPQ2vm|>*Vy!gm^SMrK+ zif9S1c2b4S#YxAdvPfHmz?@f9(M%1B|LtRD4`phsa=trB3lMq8DEId+!Y zCss@d|06iJEZ0)aOkqF;7t)d+Wp8h@L|T$l^%jetfrzFwkiNUt&I#3k@)g;D$}_Ys zA`QpC9o3(c`!;=C2f0`2maezRI1fy70;e_AtPBi=kLSWPVAI=k8$#e%G% zg!>DA0e!8Cw?eb-QD*tnE42>#l}@^$%VDEg^Nqd4C;My(w`8)($B zG~I!G4oeoB1#z7j?9N@xSzUezMEtj>BT7wz(U zTR^XKAc1YH*babsdhsT~CXao{K^Se;{4nu6l1ovBm`Ixd{%&~|KKbB@NP_RIOVUgZ z@X*-#dS74#IFyJ=h%D18Xr4G3$^y2`Jn1KW|q?6%afQAfZyA+@r-K&9u zz(2w@5)hB`^A0oCw9g@lMs!KKAlNVVAv3&LkJ5EZsgsB;)?U*s#w3c8PHg=N=>;9x z##0g{n2DmWlz|7|jGI^LFDWTVOUhMh(7SxcS_|DA(aE;{l?E~Z%nb-)IQ@F+p!~eQ zF`#Hqgw-{nL*f@-L+bqDtPz9=0ZV{F$}yUJlhckvCX#nc91MlL#!R?7BwjJWMRBbS zvE3!B3Ht`x>Gx?y=IUNbrsa;<2kSc;?)dm$wLx>8tvjKAGJ_U$ARx*lAuYTlzA1P> ziiVXTt{Ij;`GzaUmX$Pz6oay+w9KL$`$1`Q$vp1-vaV^;C~AwWd)Y+t%q%m{7Kj*X z3pHgp8rm&zoVQLgxgaQN3+3*MU(}1=ehlx;w5f-CSIzO5{{!C5Tld(@)sX_Zlih6LWCr z-j6gmF68zO5g+aVg~*pVSYhIMfe;_Yq$%D{yEiZ4;8d75b}lk`4(#rY(fbxm>3)Q< zH<%&H$^h!9>Qx)lV2nffwKNEArzaxeV0U&QVRs-fr)bEJCoB{q%sW(K6ixKq-NvUm zwmCK9QgS^tA!EDTP$?~AVnZ7+x?5ZF&>56M2F%??>g|i1dUcAonSqIgdrc)kWrnlK z+}OgbvmwQ4lcBZK2*+1uSYczZ%f5&w65_Y1sZrdyEx9z#owmrxV@jugxl#>_hy73T z+^vUr+}=r+wZcMWH%O!#&&tvjEd_S*F8i+Cy+#gO}S~v>lp` zukcT^TFBq=Uc6?fjV*047-&sZrlGFlO~}pl^JZ2sQbjstR&lj;%1c;)xL-o)WFx*r zW@V`;X|qWsvT;x`e5h8c&tCNc4x4n;uAdmkd^QJ5Q*Ca+Jv;KO*9#Ojis?Pd^^@Ye zC_t$%gB5p}nc(VVwK)T5%`A4s;Ho$hGYE^*i40KDUFd>g?ObExU8fixPj{!#j&^w> z(__ZN9O^{(mDo^86mwaZWW?sw!#f zZ7wT|^orW?Q?Y`z#U~h*oNc-ap?G<-Oe=Z#m>}+%FH^W}#X$fD_T{C}sV(ii$fQj( z=NXlRzMZU_3#q0d(`>7_lK1u;*FE|~zXOZZkDDs6}Vq1#ptXISa1Lr>m`pXw{GGKIOkJrrCbX!q& zYGTNH1?SQcv=)0QL?wqlul!L}r?-y$COAvp1=wiKUhHB~gGF7Ivdvz0wOpCZ9n}YM1?L)ZzffN@Jc;RbHGUYTG|*nCmr% z6t7e%CB9;-700e<0z)X&`k@--uN~zy1(=JMRA_yOkp`0m-Z=?!h~_(HUn#hsoqzg| zUt@4T)#1-+TxErq&HgS?L4cppF7%)wHceYWG`J5Ocu>na9lj91BD@ww3QSzit zWFIXk|JZA2n|R+ai#q1#%$L97s*s8D?$NyFE5tO@TsP=%*gt z3#rwlTY~67%$LuwS;ulC^^0yiR{l16D^JTU1Vm=A#NkC+YSJ(8CjWu&2BD%BjFLiEr3Shf%Awqo*XI-vT6F* z)0&g{QG+XcFP&2k&B|;l$}9ObBHDF>liwPeukF*Dd7`VQFlXN|t|KH;ALxuJdM(9E zX~xcg$9VWvg7VcEl{^jcD3PBa&w?(pJlEx7psH)?eynJpOBlR^7B;$^0 zI!X>drY7Q{AwVfJ*vV{Z;(nZsFq24IbH=KZjwn6^dB|l`t~;a|@HSJLa)4?pUTXm~ zE(8|0bw|SA1GOX2k0t?CLba8MC*I?2`m;|dy-#Pl2p_9*x)i<_E8*j2Q(mIiIww;! zYj5}De?5@g_#x)V#vaYLBywt5sD~NWghqN6Zw)7GkJ8Qc2X4uDXLJXkH*Kj~z

>3-8 zcQQ|;RGXDmtp1zrz#NO*jrs=oV+-eGbw;9jfjZ!iW7*i*dsV?h+w$Cx-(v<%pZGnL3N|NvjyBTLZfyY~kwK!`B84m<}TzY5wU ztiFEFsmw=~*9NW3(g2P256qb(`UMoM92!kq7|+)u?MP-5<~lEp9fHOh%diHI8`l1t zc4@HKJ0C4DRByk3|4=>vnn7{8x6@E-kTzCBP=@>PANz#<#SV0Ta2r*7Zjf+!f` z501sG`a(H3YhrzZlN~Ln5!nD}L5-Q|1ts@jIQ#vUX#YrmImlC`@Kvqzy@Iwl$jpum z)r0xfQCu8DT}<{=T`=>XRW%1Mk*0z2Zzk{qO#6*- zV(G%7EU{hfvmFlb-71>whsBOgsviS+Du*$YzA~jCRoGi&)PBgYhLbr1v0VyFSHkkj z9~dTP6IVZcpa_8<3*Y!s3 znq*#q!Kx8g|DtudE(MC8;R zK$**13vNxn_7J^Wcz_12dooUA>S?Wf{#F~TZF1s5m39A#+WHP0tgXT_G23AP2d!I5 zj76Ttvnf)&V?DZUd_l+Tft|~*E69VgeJ9SN67o=wdN<}}e8I=;fq$cWFAm|=Nqhva zU1gT)ksgg;I85~qQ&C|6LRC9X6EuGzU>_fC%c(B1seLXkZjy!I;phmOpC~LZIqPL= z27df9h8)@JsD7nqv6>$h+>@LQr5JLVbo86S(_0*|lpr(dxSAZU2Y z{nd^yb$(Vco^D6BsL}_V{fM{Tm@$Dp)TN>*)J;(!la&;nrIdogm4@hKL@bXlXB^rV zi?ya=Jh@~nN&HW6i3M{gTE}Dt0N>d{#G6=dyBF0baHU`QkZViom`&D$2HTx|BM zlT7hOnRI%*S9EdT-fxt-!-^uhS{F%?cB$D1x!7(zcXCtQx>!^Y^!hG4M^gOn+Jw27 zlH`pPNotc^?(UzG?Sc?hWWAe-^n?Z!R^G3qg_>$6bt3mXv(m!Ype$ZEfMTeX&-ZXqF?}2wFE@=v$pe#!ie~#0 z+Ra7)l;;a=sb+U$S~Sj+XE!phQz*K(YUohtWQ%lJ9qoHjwvvb?Qbs=X#m-2xk;JIP z$EbXX-IwWvS@9hO)}FI2AhL$IBD$t99cpNj8&%GQ8C~3R$0@B}4p+D#x;mrDSe#V` zTSNh>n%d|>#=^O^Yr1H2L6;bdfX!H(*d%Qn_xYIeLa!`lN$g_=b>;r1;Ab0DeGFI2 z{;^I!*h@p4oV&FP-jQ2a%{_F>WP4AAzQ75JI6TXF6O1DyusEi%4F#r-C1izv4KNf=OkB+HiHdstefA_ zNT^4}G?`yZ)i6KI-H=Lh*;S^ zUCqdzjAK$3?vhO9Cx*UR;|?$O7) zBuVu*;_mUu>fF`vTCa&}xgoi1$ob@rq<-jQLvdh$3!ULe1YY{?ct}*4SVtKq+^V8Yt!M`N=pLE4^k&eW!>t6?4=G4?eeV7|X z7tOCmXB~0!iYcC*$;9Wz60q{MCDV~l?Y8RiT4vRu(w5V0{3X9khwP~bT9GB`^)liV zR@2sl1uET;lRc}ZK2j}6>)ZkKhGOK7g$J-d!5 zm4Or8@hT6eT?WPWA!9?lRNh5i!+JAOkFwo%{8Cc$TIKv9<%MBMow=Lz+2gD{!VT^- z=h1Pl8oosi@_}e1-&LoL>w-(;(8V@aZaV)9+bZ(?cG{x#Nz^x$|4L5Qi|}m>{^w}~ zKyxu!jPUbRsB0c+Mbxl3&LyAcSjTWHQ4#fq`z^Lj&I9V?F3SfZfVz1SC$UIfwjP6X z_>TF}>HD?u4M;Wijo;iSod)SS=UY)S8>rtxeVQlKR z0sKOKBMFHvxNiGPvR zuh9h@Bex_v3K-+WX`;q5Au3SD@5OM;RL!$GV9LDy!4_${7VDD#XdGZ|NUEzS+8D?j zU{tF&vjSpi3|ccrNyj?6j_rY1OKyikaz5c1c~qI}ytQvtOviO1`oUNWB zZVWD%KrrXGMIFqcGx$8(?;KI&D_GqOiI@3Ru1I{T2Dfybo~_x-`-$Ea5BL6!u1K%W zAKH#Krs%@hgsvQY?^Zyb6OI{;k47Ghl>+02O?YK2&CK*6yZJ5oUq=cNhQa+UDVdwn zlaCA-kZF(#lXPV6iZi)oFDwO#J6PVN3p-su^fOQ@8>>JI9{$dbKO{*^>K9e@8F#Sg)oYZv_Kuo0jemYGx9AD zM_eV^?g=RP{hL~_OPvtFB*XDzD}hSvUeP-tL>X{}w0~z!K6EAi4_Dt5U0JY2n@&3H z6Wi$6b|)R%wrxA7WAnteZKq?~wr!_h?)!Q7zOVY*V^r;`T63*6=dAm?Pw4-JnsOWL z-!Z=A-!lLZAw3mP^~CKzVyaHl*@6?ITx$%CGGz$}n?L@NKVTY&g5)LDek&J^{KQY0 z3>+yaY`JRLvvtrzUh+qR%hv{=_`9%NxaoU+$AceErlL2a8ce}d!xV+s==l|?_ z#r?z9qVC7->@?RW!@%M5EwFYg1mau5UJfE6C2SQbrg-xp9%muAb&OwS@D|7$;ny7+Iq@#L zo}(5EHh!(Zm(v$;{q!O;IXlr8jTfK zuB;l~(D3lO!eLY3X`OT;b!?U?MYsh{30DMi}-`AXMaselB|x&&t2N zYH{FZ!mD@|R?#N)uQ{fKpr&%Zu>|HU%f?`tzS&*yT_Pr@3S(sii2%pnW`iWhEmqnbr)|m(9U(npJ?L{1X1_|#O${5~{LE(_yAEW#5_i;Mm5zalHCiv|D>!!`ZhYEBi92GkkGtLg^m8oxcyYMB6_)jU z2n+RB1CN@orMr#va0 zSKzj9zZ_Z~!CgU?^ev7_+hDEvj@kQeUrP90OU|yE=*n;vJ-tf3GFQ+Gin=1RLBADn z);RC?cZ>~h!d=|@!oy+JG8R905jofFwPy80?`0l!NoSSI5r`j0$R1-bEY<{TsX z#qL~fmnO|^01oy0nZ;WWEQ~$?pe0sHMS;LqK7o zkx5PAH<%SbLii?OQ`1T$*=nRsE$I~TS6(%qm!*XL5HrH_s^H0L`7Mg9vzn$Yh2LU= zqx5b>YK|})i~fhqmvC|@%HyT9p(LY?&W{UH^ZJ`edz0%G~Z3@Ch)z zfnP@R$*Vk(px6T4pxn+YfE1p3am$LEYWh(*ow2*e&2Ia-Wyd6@l!--KrkEUz!t9<^kCJaVrNT4|&XpOm6E97icjP*O6 zGg`+mRK5j_`vl@0ZN;vLl!;m2h|{Qq%EtZMgTn zsO-FfywHfVW&WA1y^c$(Fm9Li*y8ffM@Yz3v+rm?{Jq7$?YJ023rF0{~5rV1Z1f z5P?>WmJ9A4p=+y^yXhiGB1ryplzh)b#1gkTREjy(YBoT zep~pfX_rl|iuFqVTB>X*6k$ocaid$cDFyB^0>j!y_Z^oZPZdUM>S@@vB2n46^4ZCe3>Pw2nWek}!Kjbi0UPqz0zqL|X2mlTvG!GUFOC zjSyNJH{ju7yW~IM%=uKN*zJ0ez6J@*#&|hSVRUQuZrkR_iKlp=ob!D@qxg%kx$Km* z;lCxjuCbpyP4~ZlRKjQYC+C--&=VdEjQC3rK>(T@gaQVsT6m!@q5Fy@vF6_pt<^ao zAq+Txjd>B<5IG8lN5O&*$xxYR9s|o?p1RYaFdv+#xj61I-Z!h9GUKyy-r{?U9u6A&p z7hSz`M*Iw9=|pb(3=XzCb%))Wyb7L)bU7BB)BPA5 zGn1KeMH7~r%JMXIrOxEz@bI=hEC44f)L$#GY0a|mxHHYA)if@H)1)|z#!pZCl`NZ? znpe`TBap5^jb2dPVKb2;l1s$yv)N%DjV^6vL3_dFVP%og8*Fpe>+m9vBh1@AFO>52gsXZ8NyD159>V3FXT>!c{*H?o3oI0u|6wIpJB>6u^)liM0Mw?)y z$gNOWJMhYknXjNv#*rx$Mqbk=U}8eTB4lLhORBbV(ip$mkPYd^ugI*LoFoKh=cu)x zQyo_wIF+3zjuPZzn#OGg^oPD&E~6!~8HQCzC-H!LoHj3=E2-vtMD81pLC&E%;Q}?N zk0k-D<>;B;7c7O7aSwwML5ZtvT_t7Qr)8cJE0><=qSnm{?)@Y5a~iAkJQ~Egra^Yf ze@pI-h{9kYpdE()qB?Sd`(x-lcFdYfs0NK|4XiTUusoA zg@n2{fhdEj22)u%^{UO;n4wPI>Sf76eV$X3g!OC3{khA~V_Rof=Vi-3{%IiN0dmRG zaB0w81qV7I)-w{}%=I@`qz>{`Wu4A50lZ7KX^j{yo`m!|jiXe1TG;B*$1i`JG}ihe zm(65%dA-&8tt--v#=E9MtFx->=x~vG?QMxui(~Qh#}~fHL284YF=v?&uf?Sc+@YGy zpx%KWtsQ%>omb<*0hWkLNVh<1f={SEPt;TK^)0|&*r7~emgYj%Q52EpLsg#sQ4&%* zx2&5Jd^>z?Dmp{Lip5!n@XIQ^j6{zR{8eYGmJ2&_JtVj98-Q$57EpkrwPAs>+TS7| zT^_GJQ>Nj96k279G;`N<#szhkSM5-GGtpg;g{dZ2{N6gw=9Q6VT1wkV=OlUiqE`g!nrF&km6;?Us0u zAkNW^*&{r+u@9R*^keh}K7LNt4sfILc7rzD9q$I>izdxgn-oLGaDFeSA&b;qUhxsw zNjcl^N}m#xO$zlzq6ImTz#}rn@_EhQJXbrgQ1C8~qBJD%F}_ z1Qj_v*r&&KdUNrb`#A)N1C4M5qr3#L9U%}KuaCo0hXS2iSH01O`wwn2FMO_pp49Hk zm|+30;@Va#1f}{*5C1knZ80h}JOpjfbOdENZuu+ha~n`Yv6N##EZ z`pLr8!Zyc*uT)5$aEflAvP=T^-c5_1D|!5I6E^a!6BpydQ1TmYqxobsQWUa=07fNz zEX6Hi`G-v2X&NF}2UO!E@H))$d$yH>8=8sjV3)UARKOqkF>d}IuYg-0)fB7XDN)mx zoyw%c?$Vk4FszQCq!V?*v%8h$d^|HlFHZ7++Rj_#@C#6keT5$1yn3F_8yG|?h9=}} z>b5*LYIw+Osy%PUWQhY?=1lNh>f<#G^rGM^0+ikS_T?Q!5w?3Y(s+JA*rk4ad+H%y z5qs}Gsg7{{S(u6*XW-N=#Z*eeO9pU|8)p89%PQ9g?uhfp?AZ6NzZ#)kgBCua*U_=d zkc69H5+IL##zT$}Fc+cU?V=kAGiYr zLrLAyM!!Cb{|$uPPXM%$Yq#xV1gt;M_(5to8hd@=!QZikB1``9SYiUC*q&G)@6;0Y zi5Zu}?LS>y+}=)j55av^G9LWlE1xp1Lh!9br0+Kq^HnHchDlMsDv*FxY0oBCjJajf zId6-CV1-A2Z6o?`ywo4|Q_d3lSK>yRp<{&K7QLfH5|!iD^)&(*q*n%VJh>GgCpg1i z-sQ8kOZeeCjxr>vi>i`!1{(}Wtim~1MLORln2%1`$VDu*+zI3Xv{-NyYh!z0*jmxv zd;9`MZ9v_BkpFM_{xeUIw+R{ASC8z=w(nQF~vPvz1f5)zefo!Iqa65*1@-nmW zUY3%ujfIDUWs0S~eoe2t```DP=sEy)xdNJTq`t}^yCSu|F`hAL3UETtqRscXw((Yd{B5yv>Stbw)Bp|lv@){gjmsgST&5afGK#UF z{X>Xbhf7s%K~ShMvX`Lsx?(^&`$p$NeF+aPUU`k^Ff0emaugV5+3b>T;1DZM7zeQ< zv?)+h$hVejl%7P~GFshb{u*Y~RN;!I1;`I6GANZDD%!mhIvSBb0HmJ?IQlDe#u(aP zbmhG}0BHJ*F?pLH+ApCI_E8*;iJU{G)c!8Tf~n)SG3{SEwwat{KUFm4YvQ=D$0771 z6zd#46`nTz^8&8ewHac0D^U?PaW@rpv%hFFXtNEK<55I&4q_=TMQPzN)ZmLD7jj;b zpo`&PS9KZ>Z*E^QN+YNa)vyYC_@?kxZ{^gq z_yDy2wN462U!@Zlxs|{sVjOoS2%w5wS*4KTqgL)UtO1EfT2xxW-b5u~P#Z)89pFF> zgF4XbMZfHi;>iT!XGUa{@Z{V?nS~t>p+)($iSAWLT!^V!tuc4#NsfN~ao9=v9qB(r znE_F_E{_;$#UifwiY2n%NBaS3e{B(8rIvT(lHEZNOCRSQ$~7y>$Pik{YY0v1kx^ls zNS~+EKM8~m&qT*tKX;4?kY2$&e(SC?LG(bfQ`n~SaMzQNBn9dId`#rE6VBowaf98s z=*LsxlFJl_#R$h;Gm5+C?r9WLCvU)dxycIr&eH5OqAM4#E2sT$XrF)BRV4no@@GW5 z+VPR4#(v=Vf0MKSa}JpGOk#?_K!07<5&z%efb(xJ%LQ>oN&v0o=Je5q{!OvBRh}8= z5b36VH^DRpAR|o+ACV1)k+Y<1mzz~AT0SK-WZv8+dJ~65_hb5k@h9By+N1{1VbjX2 zI%RzsBrP=nKR;jLb0THXVbPRH6{O=?85Yv171d+S$f)QBYte@2pawSYq_z5M#~B}c zll2^j7BbU@egQKDyr=^&m1!^A+Qw?M8}ajvIXeta3U$V4RnAR85uyudp4%L25Gy&9 z=|zm4UV!pe1F#pHVD$l8dA|Hv`|^c~vq>7==40ay%PU8#be^L5zP^;F$pUAJtn?}? zxSzi_-6Cts8hbSE9|{ziWjT}Ol-Bf#BW$_3t#b@p2xfWjpfb9YuU zz(Na8;QU_T-Vb5vBZ02T+5wB&#kdRc^?;9aeCF#0a!%z%fiL@&ycNC24uQ1^V7u?( zGXXM@WMdpujx&0pU_v#THfQL3)KX!Z)D4*mJPP$uGT%1k2@GJvm#56u1oPm~@6-jm zNDm2A026l}Lk&Jz=}gtReIT$+p_&5>F2DD|;$$*z^x-Rh_+4qV0qRqD9(qfBX*6YY z+yN^Blf$#nw6Lv`dyY7yRH4JLGb30Qa^exwEGqiYp=ja!$Qu-Ff`>$Rlpd#826nm3 z5N0RV%#1@^@!L`;W5?VvrATcAf{B8)QOnH;Ksi6{Q$!uS!afE2ZWyxw}Nj!uxzJ>Od$FY=<#<83gXd-U*L*_t~VHa3cf2v-|S7Rszr|=u&#_S zMOYDAE-VS59X!7R-;_@#yNG(aUODL)0ruF_Xr@BLLUoZ)@H}8^{7KTC<-M1|I#0L^ zJcYO^cQm6fSsrEBUxJ!orCB(*8Sgw@|ts{N8?SB&uznikg}V+ z%MqC3tV~%S5~4F4YmIP~r8meBUT{O>8L5i(fDL76 zO5@*A%QTf335bQ|+ATO%=tmJJrLUN7qq_msm;owuVtiJ3=i*mE%e7R@+AdbTn;2Pz zj|#ESUP)82o4f$pr4=~du~N150HK|*zYka_xY0j5!|A;BP;3iNJzeTA!*sOL*w&a~ zy@)b&7S3C*y-!Tq)u>sH8|}qpik%jG97v_LI9=+4Xx&6j*^eV}RcWXn@&CAb`UwNC zpR^f>@Vp+Z>g`Ks4kx=RAP*)(Eekrcx|e^BmIA0chyT=QPfal-Esz=igB)h8Ft4 z$m_d8%IB=f;uneR_M6=DA6kS1UM9I8VhvLA3us%J$|iV4a@zTR2aZf4O`!tYs#k2i zLq3K6b{WL_hy`aFg@62(HKMe{k;FlSrE5wP4NP#u=MaoPYmvwj-Iv7bqsvPcz-S)X zD|I2s3Jc{gl#aA^WdDcl>E~N~Z0Gv%8irA`K`Vha)MqL&8YLt}6-1ujOJm$9F1x5R z>HK4w9;?`2V|2)#zhBOPyMc>J`zMd+&9{9f=}nzF@rOD1!y7Oo{{L^sCZ`XNiGsGR zrN69u4c%7D0?s4_gaxp)muG#r;pc{>Nm8*N8nw~2pTYPNQVI&gF?wii9px@RE@3_X zx%)ua2WNteZL)x@-G^HyBnM1d+Ic}8pOE#AM5g}HRg<3FQzsi1Zbd{4kG>*9@hWaE zCaC4F^C>D8J`;*~3AI(&!78csw)c@8n;Wi2?&0fl%UQOo%{2iUE8vHd28YT6!Hq2z z?N-_U3#Y6#*_r~s>OOBMU|_%gLpcIbrV)Xjr|^Iq>RR4tYS{m>jX@gZ5HgtH@a8;C zex&JR!C;jzqC%2nHsaI24a2*eKpQ8f`YiR6<0vJ~^HN%kPljs^%bvL+bky^vLgG~B zY?ht;&*$a*gx)9ZuRbjG=`yZ;}V%?7EA~Owg0>eU}nx_eF+&zF;Ic0#R6AlVEp5NjllO2K`x@q?I+<_v!MUH|l9`Ps!vl<%Gvy0=8CvGrD4v?z=!qv7?UZ zl4M9|_Vy5}o=lk#0?#F&5m;>VQ+Q?B%Wv~h|5Cb26ikI%l@d>|yA$FCFKdG1UNKd| z&MjHSakwR`<*-t%s5Xt;@}y!f$pF>#^^|Jc)6{UO4d|vA6vt)t@z-?gH+(+|r1BuS z(^TnrvaQQ2K;|mw%PnN>=E7s$hlu*smIVuC)TGOjqJy$3S8X*mrHKJ|9+mzeEJCbb zMMDzxPS42YPezdZJ-Farz;+oGLb>+hTjSHf!xD=%ZUfT$z}Fm;@*5N3W+~31}%ZavAPs#lKfG>JgUMN zy8fxvylUuXE*gQSgi{=i<_9;@}%iU3)=*+AVb zU3Xw@GGAHA*));3>qP2t7fKh^)@nGE-vJKeL6s4&Fmp4oGV)Kt&un-|A?t$DR`+eDkY?UJYkbS}h&k1>k(oXKP#6dDw^|__3oR`ZcVg0?FN@8v6*z<(5 zzUuWd3@(_cM?RpZC;G>yOLPF)H{8B(d=i4onu)7D1udy!7f@Y(8a z&R@iK)hYsDB||P8HvL39nujeNwhjx(fZrOBg~fJl%eKNimkv;`XpFBB5mj>O-$ zS;lQ23xew|R+{PYrkHKG`}xJ&KRXJl*Bt^J(B@CP)F0i5y3FD!%yRpU`6^K9WBXzeg?A&Y8U5zBxvlDF)l%7ANEZ?8^zWm1%Jhtv8@8+^(8kHUz}`BX)ePChR--{!(oCu#*!`;m2s*8~D)5K2ews zs*QzBIpTuaL=99#=2HsA&Yzg%7&gc!rA0VIPoiO5NX=LdIMIL(1*E6j;N0)d3}b}% zq@*VUc-#vKVW4Hm&p9&`9pcY33lyI~`Wvp-JoZSL`w!~ptx5@Re$s#cWy|~@g#0ho z8)@zfA+LSWI5 zV~sW?R<@-D*jn|b%rr%(1_&j!vZ7q1rYa3+-f`Rs2N$zj>X1<))en3~&&2j%qHE=R zjP>rRf{!elCCByX%{`vB()vBH1 zP@j};Y4p{WLL&dsZ`t(BR*;EmHn-%zgD=As>RGFMSTsME6mYW)sDWcO1et~Kc}di+ zeWj;sP<9d0TF+dvbU&kfv8QUN3?mEqzveHgHrGi6 z18)hW-qcXdr-L4cc787N7)85{A(V zoZa42?H(a**0PZD<)4A?PD-a+%5aLsU$vWw1uBVCOqh3uqTV0^>avIH^nB+F|8PZ) z7V@;5WgBBZiSK41IuIJ|rx!*F=Q_!M+keiO zC1v10oXQ{*k0uomqafZPX+=AZeU?!#4Q7r)9?B^8$a%}>3pe-49iY`9D^H-L91bvf zM6ML4kjS5ou}<)nhf`&I{V(#;e|G-PpDd3pkW98TP)ZI}0qyg$VJ_(etnl{$CP`j{ zP;NpxsZ3ePp!g>G z^$e%0)}yz})-A3~-{+67tc%x{Ce%P%0GgcJJQ3AYF{OhuMUgQom9k3IAxePWYke+1 z#VC{?xNEE7wj}&Vz(;?rt=;;@QG(NsB0H3 z(?SItunpL*?hhK*3!@TBjKAu;xH}<9|0WK$S9&_h!YcxoS>}=N#y_EbWP$tV&L`5 zg8}pd42=A1NX)|pWlsQrQL0*MsA^an5KO4?-wc)iN}($=k{Vi9Cc`aIhv#Wm55=a+~G#p>+Jkkq4s8Nl%vHvxSV;LPFZDGUHx@KuMm%q2B=84 zlG((~%9`Y-9CSnds}7~kNGrU;8rM;R%#AzZMGW7M#!sfpwqvXz2M9dbi^-bSdZ zQ-A&z%N0uuS93kDB`4YhWZtjAras-rlB6@5Xx(WZuH6G%zUJ zF_^La0yp4ZW&QcK*jk5h6K{Xm+)=~w3&+0C-l}4vF-)b?bEMwSKcDvj!(`e!#2v^n zvhTC8+3Dg||1;jov#?G7g=MNBAk1#yx6SYS>suOM4pAG0%2c#^jz2!FrYR!C>6=23 zGR2C4t;T7l=#q5d|w(SWxyUD$HD?S6S2uP>-m#QoFYVizT#2 zaT_UP<3kY&1Jhs4IsNl-2=>B)k-5CWs}F8*!RhRPXZU!y`|X{@J6N;#{kM-!rsrDM z_=ul+6zSqq)x2nsLzS<)7$$B;GtBDi!@ZeWYAYq%Gzv&89Z%!8?ATjRlS)05KEj0K zVH6!~imoB1SBq3P_HJ`pBx0SEZ(l0(7|k|N?x(Lt~6opkv{l*t(?%}{f22n7`X zXm}2R>JLc4&qlS`-O>sL1zI`VL~8h){fO{$>V$_Ooi0axo1D8*=Ojb6htVW`E=lFd z$HIYo1LHK5sY}N#cW{|!p~k+0(JsRE>GJ5~1Y5$=JuK%(1yVg%p;IQl0>;*;VFAWe!VgYfY6@2DB$XjIWNGcf6iM< zML(<|YAmZ}in)uHy#f{9elA){w+C@{`xi|*(BIsEhyo`52jojEq$0Ruo7O=FTwFe| zhMUkMgn?O0x4^XS>=V6uhlf^i}8-k8z z2>x7qH^no+*%hKa>t~h>g023OB739Q&F!1bQ;fgk+uF6`@-G3+;>&B z!MywUaj*q^l1^kr+&=WSU23-?&h}_vddi^9`Zj+JHt*jIxwPwo0eeKyc;akT@Ddev z$(+q1)mD+LSO>SvP8zi9rj=JbPQEwKmQYm3?}EFbq(i!wv}9}6D^E@Y{CgqXj05?M z6MKi!KP_))PdQO7$a3qo!Nqc31@qFz_#fYDNO}TeVpg}Ee$LO7s*2&}Mt7ZHnohj` z*LxBF!3eL5(|koB-b8z#+=rYG^?lD5-%O>W9yU~6y#CJqa{bqn_GRnc(bbh1SjhA4 z&j1-hdiGxlyw)tna)t4Sk;FAkIT(*PU0Ko~bIG8qI9013mdrD>M5qm*p zGi)CF&`2bgox{+4(&adJobjnhuCk3o1JCK^%Ac{KKlY%%{t!Vb>IngtNU8@x45@-dbc!^kDV;>WL9_?2aShWUYD&$S)Es0 zN2A%F|F-@?+H-uC5{LPRd%zQ;tulg14RFB&73^*y&_&=K?CPeYQO;cxZd#r>si~>8 z;WH`CkAFAf9~qRp=iy`4U=4w&eM=nHVpy&JDp6-G#tITgoAp0i<4uL#co^Im)!tED zJX2XSeWco7JE3=n?j_^AUYEOe{J{*GkfPRAowi8U8KYNuWRPu-R-u+>ahT+^1E}f% z%Q-nM5<<`$WVC|fcU19}dS(t6knJ>CC4&T}daeU6(4c-q$1$V@IEh$Y6Ze&-yg)qm z`YVjesgvEM@H)6BWbuqfYcNSw6_>FR^4YYpw$^K)+UGbM>)PWRjZ-OO9!Ezna#*L= zQ1>crePMwr!=I`nj$-K6qpVnXt};VFP9@e5Xj$q|yqdcrm?ZO}vL9yBM}g`}u_-l2 zk-HLowf*aAO&(}>Fb30(MgFl1#X0T7+#V@bJlb<(@q95vkg1JzQg}_>W^1Fr1QwCq zHp{**H$4|uS}s+U^Mn#Zj+o+LjImKaLM_LvbMrdT^$9fB7Ip3p?pEoHU0sd?e@~Mg z$EevX;n5t#EGH3hKi>wp4pMiSEud2Szm;iKwZSRU;XbNW8g8Pgm1`ynB`M`5I!dlv zBX#@*;A_;|=y%xMi54cw;j&a>KtzfbPpDhenkJ318XtXYS7$vUZS8HSH^(F&zca1X z?H|!Mf?W9}THfSd>W0#`S*_xBIwY1#FsU1`ZRXS&BIpn_O`3_uP4sZ} z!Phvq*3frbti=t0W2a?^|91Tg|7tfry!)8b@9OlK=5vs5P5$rrxxYe%i2ZeM#);A9 zxq;-9UG;sjR8#t7x>*=2uv<$zGS^UIXv;xjh@Ww8)Zm8zTrYNePq;iuCNjz}98L17qE}o(HZ8qkD0i3}L!(p1nGJux^YJY0ON@Dlj~J)pVpp`_ zQBKb2zpSyQ!Fasa^#_jUV?j2Fr%Qo8z3|;J#`2P@y!rh(L&p&wD3!B?^+y0{8GrsB zri18s8M%Q9##GEHWY?D+6WN`QUzK(j;gebVmct4QR$){=c8@ktC!^l~?5>^DY-RE2 zDKBb|z&qfU<6B`efwE=Rq;5cbr{DBBTVOSjAd@7MDa3ZU}kN6M< zBlWSqGDRYPni5i6zA;Jay_KwlK)Z_~Y{ium`(Ya`BZ{8@!3apjpsCLAeof?ZTWuBf zoBn0sGWr=Nk?w40bk8q78C>6Apxak;vJ#5KM*R6BeGB9TNj=piCwlfG_huF%taW<{ zu4f4STW#Kzm|S0zESzMUck>Nx`%SdgT^FJ_XAJZp_5C|7u*{U`#Zbv~3SH-@l!m znCeSD(7UWyzA*T``&&Wa#8VZ1z7P9vUy98rbuO365HJlz z50elWq}$soiUt$V$bo{I7k2kIp6`{_zIv_>p|$vHXZ ztI$s8S&}_R;}t|gHiIeHpvyo7t7IjqXV(4wzaF9e4```J{5$&XzXB!`ptM~6tHk#m z66EWr02Dy`Rqt&b5h1d=Ylcpv*>#?D8sUeO*dLfs0xPoxjkRczv|;LI@lNvzt?OMH zhB#;jj8fRkznQG-Y0zh63T$<|%<#HgcQWgKe|?7i4IiU4eM1z4-bh-LpOhCJ50~ts z?7}-=I~Hp#E(RqNtX}Kfk7U;h(NnV+7wyhP0DK5^8}?!ssl#ucy7+l1(HP@@vmJGh zec8~$uo}&Prw4Vb>%PbtyrlQ(W|#3Nar2R9f$c z2SmVDe*=Fj->f}`es~ErT-v3HnHkex1cA98%6NaK`4?CBJ&fMi%8ZCNbVQ?!8Y)RxtjF= ztPy__z~y$v&=?TnjOYpkU;3ugGShVbf9>l$)fAFsBrvdb@-J{k2h{Y^S6)c{=QA?C zo|^t6@OP|$A7w5nG4UT1;y^K&5=vN0VZintxf3cBIg7*P_t1xxGL?n}z5>X#*Wjk| z_6149$f+uoW~-%*h02XfTf(!)+VUo==Jt)I_PTDTFZVM7Ow^~C45!PlQiJZ%o9_`1 z#Ma05$q^Xs*Xjx10Hqe*%Pl`;*sjdoIRc-qY4CmDHIK%-&z6I~xcj0ZRj>c14N69~ zcKH`>ffmo1!T;#{u5O_5=WfLC51t9(|CQ&X5GL#d=12=mDl3(Red+Oc5hZ^Q;mxqZ zo~BUb<{Y24OS1*%rQ{i9OT&+{vME)iomdu;P_mR1jsqkMf#mhh^Lsv$NlL7#mHEh5 z&}8Gfhb4!~jeD#M<0^nds;)Z~rNWUDZq*U&G}`F`O|b~t`C3grRq}`eQIID7*+??s zbb%~rfSa8!lh(*OIiJqoFB5HEc*aVwCNkzb7 z(YO((iE_pY=wGLL(n3#pw%}Gyj_Z7`7`YbcyGe$7;%d@M&!qyA5#SPAv9F4@<&ukH zhd%HgEi66&*J%=OC&veeiJZ7;NwkhgZL(SM*U*AioDuF|NsJtPTSfVRFrAeqi%+vg zAGc$%v2t2(+I3|`-!MZ*fgRo0Xnmtc8{fp(!j(G*#jx~P zi)%vOrCE=0=eTsIHr(#(j{Y4_UzYW2-9(X=YRTgOn3y%4Gd-n71(cds)NbIZ)4{li zzU`U)jP@J&aKX+(&ADUmQf?vi`X_$qO_`x7pz8{+OQ+Y z9-GAw)#Dm&z*mSq#D{ zmnTe_Y>xdzK3gXxGmuNFIpIALCO$-Pczt^A+?5*H?kCvC8@({*zOj-rxk`qbo@IAm z@m_+1Gc266WZ_PlD49wJD$1Od4 z#gD1A0@Xk1M2S(fZj=>mR`(whUzSxbq-hqzdJ?z=<@&Jt`VV|r5+fuKBAl4#3R2U| zXy~qoN?_k+eeeG03v^9T!2US}FP9T+S{(y0V3mlfy=KrJz?&=2771r2qPrWTRHC z-lkkrW4gOubr7WOsDJW2nP_n003WxjPQ8<7f`l?bWpZ^7yew>n_AFH5CK%LtITgY$Z5{l?h3A0 z&ynZG0ba^Zm|vx2h$)tf4c2&$nvm$UbLM@=*51FEH|PQj!o%AAsS+i?k;GtavLeMi zEw*3&MX+{6Q~nAUrVHlSvotf$Ft| zi!4qzF1?=O(D``Ju5>N1nJQJ96uwoL%2U$eR@4YNaY<0iHWnAYRf5l8m?H6pW9*xd zZJZYTOMb~CP+E=8rFE&jC5;z(Rdb55i^q94fj^#U@9K?N)55qr-yyZb(u_)+J5+BeUKh$=p>X zaaDg5JHZKdmDP5`-qx_C$;r}iX!toufs+h0qXRmK-Jl_GwivI`IJlnv}Nx9 z>NIqOQMvEPvVKJ4tAl(XJfLF4hM557Fz|78ibWm?KF0zx>s}+WMlKJUJUbK_S{~Pz zEWcsZxWlA0`F~g8Jg23vw~Y94OXZ>e8%owfR<_>CQcg82NKO%&I#c$>x5d%qiu5!j zOivh<|0IcPmO8v~TLxXGn-^Bq1Xr8tC06~Mm9QWu9cS+gA-ESM#yS6I-D4F=%&Y8ck@|Om%;Q- znK^t~$2!HDo-=d4vh%tuVO|r_y+_DXA6&-FWC{adKj=xKj6j8=7-hxHEvoob>8%3o zEXv`*rCB>n1Fi|&Tl&7xmB&C5I)Q8#Kg|6QYz%)i5<5M?H-da}#Q7X;-}|u5anX^n zpQ;|bBV5a<7V#S#)EDET?&|!+W99fWi;<16e7;&DF7)hARMG}0($v=&NB-(Fk>JHi z5sVh-?s?f5{AEra&CrxS8sUrn``&2!1-3UAR`I>?vKsRn!p5E}P!kJCbVRW7oiXF> zuFpa@m6HJPb8|UIrqa~&C4pm|)QIkbSN9>#dE);xcGY1~HeVY=LK-EO?(S~sX6ck% zLK^9iSV`$xx*O?~mhLVEL68(gx)H%|<@de{zW;m=?6tesb?$S{oSA2Ko|!rKV2CSo znUZZO`Ix_mX4nK0(mLK?y}ATc?^sTSnSFl1;Dun99z|CVdypq7d`{MwU<9VV!2Wr0 zUZ>UyP$KwIDbPhdUx^1GdNP2j=E;{i5;gY(dNj`~BBd57n8MMt-=)*b8v;3xk`n4wAFhgKB zU)QM@zm^s_1?=6NtHI7JENPS&8~Flt!@x9oN%NdaUPhH1&>4X>VoNGvu5Iux!dR<+ zRW-efVIJ=+(xiU5sb2uG$dhTOlFts{jFIP!dfW`2`EW_xzs{Im zLD2X0HT&y$kVxn_pwIt{C?8vd^<#Yn{~+|vxT<#VQMRWaupQ_b<*H>TtwGEqte-W< zmI&+xlG3a*fJK6JPr<42w%s?PiS{>7-U#~3tFh9YV#P=1WFo#l5Ne2d!=F8ZjwoH7 zAGnuV-~m7Nn0WW#d8&IY_=}3Q)leEi--N8R0%IbYX%mIv(+Ez@_H2FtI{I2!_ZuWc z&M#^>L>kkK0zZH`LorUe(*Zhir56P|&t}RtYiFjJPML;rhk89(C~k;cO{MhtMF1&>+0G4dY2nP36D3pTs z>c^lxB-K)T3vSt_oRc@Wm2uM&pAMB?zKBV@Mv&ph6D<{rG$p#M)>-P9J#bCfrfyRt zadH8CHr^XebU&o^h}Ufv^$qJkC_1^!GAHZH`TpFnz8UG9uOFzBH8h=pB2Rn#!zVl@1Wo z>Z{3EC4T38R4`L&go(kU`e=}FK+W5avut5#fsdb;p-_7OBZ=s&Y|tBRc^G)o2KolQ z`p)_BX*S~qJtM%TqZ^NrUr*_?Xy$Q|ePu9D^*&#VaF}4sldet~i2~hR99i`a(?$gc z;B1Nnq0j{}*s9tZNvCvQA=QPsDP45sR6PxgQj==0s~fB|b}X&YwZc=WTD9a=j+~me zO7KmH1S+p|(fq7_BBG|p!mz{sn|mlrW@$sg*@ZW=vDOzLNILG{!JXWH1`7U3iNrZzgTFM4KZnOr z2Ml&f2eoGTd54I3lXhS5BkW<|=l|@%yyOp97cN{Q^@#L-RV399fL+F?@2_>j&4iqK z7|tK~pR9nTK1RP7Ht>*Z3rO0~Y^A7VALSVIMk4&k$?Zs)BSWnjQq!(fq0CNo&PgY8 z$TfYi`KAx>b>=5rO1MQjF`m-~U8Z>qPd89j#GFv6#D)U{C8G((wb8%1y`w37U@}Er zEJIE|%CM&a|A^f45jZL-nFQns0JT=ERBzL*UFnb#Qu5`0hSNNwt z=IYRQmQ8&UaLUFZ!L)(4$Y8;XtE{Rb5b`Nf$aZ>5OO zad7{{qUIz-2?o5W9um>9>QfQd%3Hgn-RN5jj@I5ZHMRVvCN+cP)-WsB(N*`&a$wy` zf(PgmrD)_s(*o8PL65J%``oAa1BEpv3qK@eIYrHM@ zDtm(P)Hit}(r1n%V*b}KSGZ@|waPUO`0|tVX91s@qOX0kqTqI3^NB=XbMJ_Z-8`$% zu4#|DKHYh7kbv2l+ZcM1W&Bfa$^d%c>!s+{Pni4LC1Q-vdkY|9FGay%J4Aka{>mNhL&qB7{M3#NL`ot&($s=+=UQ`{Vh zfcG|znJIWO?%Vs(=6)M+l(`o1g7?6?LQTD;V_g&3#j2p)&t`zE2=}ZKHmamWf==hD zs`~eHb*(bq)8xa(gXK;|UBY_XIqYBC>(Wp^>altc>w(u6Dq^<7FcTYg4xUd_y#GYX z|Cm#@i!i*RsO%W+#&??G>eI2Fba2k#6yr@|8ake9?T%Od8&;qln2oAYJ895Wk>jL^ z!e7NP6qHTfkUN#H%x&vJ?jDL`B#EfE*G~8)%dt0=@kRr3$r!M(XdNDn7n#4x0SC}R zcN{rfH<>^lI!pJ)!S6$_4HRQ+(KCGCY%%-+uy>G>AoV;}OslQJY!U_7`=Hx|>AKp3 z`ICF@@YhHpE^1SKu%;t(xRky{GQS!Iha#!@75aU zcQ>R2H1lCUQ|)E+n>$;zT3U9!TDvlszrBhUg@^xcQb@i4Ryo6#6ILBE{QARxMHD@B8wcQjAf1vin1Jwd?u-&nG7tlKxksabO+kaYxjk33!5nq_~T@ z=LKY}ApS0udDI{#+6R3|O0Od{pNUPzUPY~|OZaq~Jn=PM0{|eagm7ib50Pg3IdkD!N2_@_kRD_>syU!AB-P0C(xAHC? z-XutpP4*xjI9{yCm_)c2GKw!eyl~!J5_H}Z-W(fg5XowPK(_{B9m~m%W7jyzqw9j6 zam`Q&Vn|la$);1(P5y{&A5!a#Y){`fin-~VL0((e``KZ7#U8wR*LYly%1_&fKJvYuIX0o2Him-#y-1!;4i2 z@k$)X-$ZP;AZu#Bvun2&uI`<9Ey$pV5)yno!EMGyHe!Hgwbj$Aq2{36Erbh@o>vf= zhwe4quxX5zLQ7)r>yRg40V#IE1y7~C;oSQ9tfCI9!gkXpbnM7W+Ka`i#ncU@H1s(Px6Z&?&yb;Sl=i)c7XY zm(Bs$HHiza$XT=OqolhK-m>Ol5?Nd^B84htiJInU%*lLe#GbJR&;6ELB)=rI4 zuF1RF*`prvGPN%3U4AuUIoik^mO~BvhoCvzhlb>y;*BQQMQ2zGR^NQcR;0fTte|2? z$Z)E#kz0_pmJX0t6gi;H3MR)ky6GbJCr1uXu`9}_(f7Y;jRP$10rg*nG1iGIU;mYr zecdEQSqHcLbL$Lj-h130P zyd8WE*MZ~BxPtnaVku!Fe4$lNUZ*cCP_yiC#jEwaEhIiri;4>TsT1+>hw8~-jOvfU zEsgzYS7P2&ov+Kup4v-bkR14RrP=LKF;jFGFqp_lCgzxHO>=uib&}#6SYUpE^1e;P z&bYW1RJE6q70pEU!y!Md-k2^FR2m6Z3zfT8k4VCmuzb^IE<8%q0j-6c3bjt2=3d@WvuvMR~ zHJks&ZlGc(HC&7z+=k$+H9w;tU2Y}ud9nr`LWUu;-Kiy}r>+?8;7IZ8NGS_+(0gWP z*Qh5v-W(B8w%~HAD&Al#rTiqfV$B1kJ~5n`(X+-9Xr*fat|so8K0U5AeA$}He?(C@ ztik!|L~>VjTq7r~7;XiAW1KMFHZi;VKt)(z+X>#aI+<5<*hw6Ndni?zFgHT0NnT|Y zXJI)%)O@N8q0DoVUG{RwY8A1L#K3(QR(OPzbZYQP4J|Y7v^9QCh(YkaVQM!`gMigb z8`h!iv&__bu-v;DuCZ-bG=s~qBv0o8o~DGaBjCj==?0%ONmYSRS$ufT@}(&cp8m8_ z<|m($B5Joxx5~~LO)>GzQCD)2fPH{7Nul`I?zRZf)4c9%uS?YZIFsGS&#^r1uS1d4 zgW>fg#rj}LLg|M1dWtxnN~GHlpy6k&p&$%gJdQT^1_xDo9!Eydv9PC?kc;=3Lm!!F z8FG6174kc549szOYlD0=8yJf+LCaDJ(Rf8(OLdVv>JPKleq2z;xZ`{S;4WU~$f`#z zSZM+;v%-ETV+x;#fa>u-XSya3OQ+W6Bl;Qg6|FwT$Vo*cFV_9RQV%^{-9J^&s+j8o z)0>hGa&Tqh5XWIdF0S1sWSK?ysF@R!TR0C_+a1sqX`*y+o+IiuOR=OjY1U-*7Wv0X zoJbUZtmOqUW)#;H3f@-U7u1VNoQTg}*5i|O(sA|_4@{Lid<&y6u4n4`hV0W3+Y6## z5gcGK9~u%7@vjeoSO%XztZY&`Hru7;=ODQtZUX~<>O8ucDHIi#O~BNNN}Q-6D#+0h zcUb#aL5?(YHfiyZ-}-9s*b}i+^rIfW-*mWf{Moh`;`=nmXUqpvVsZPv2~;vfKhVn4 zl*z6&@ZKA1YKYBl2AXycW?M_NIEsK(v6-nF`uJ}i&&$U|JA{EYrm*Jm+xjV71pt{{ z?grp39Ptcl7a)9>AL#^60cZ=U@}l~Xa%^gV6g}RdSsMv45Rp1?u^8+d>g3{!Xdog< zTMt@w^r!I7Fo{riNyGl=GvzSj9BAq9qHV<^n)qS@9GAP_Wv2+5U$H9fn#QeI0n&~! z>-VEd@#%%8+t|lEkNW<=YRQS;qs7^;F+$oaO6JkxsU!~{NrofEL19`5wSPCPoax$R z(H8y6`#zGPuL%rQ)FUlb)J(?J2>Ihdo+$f#PG61gj~;6uz5DUt&T1_0U|3}ebTzgF z0YaY246%y_fDNGg&oPA?44QY{s&R5&g_zKb56k2r#A4u3naC1w6IMcJ?`W@UEoXxp%9@dp!nvuqKeBjz3Eg)Eqc9gi9wmn+z;R?H zpO-~QP{^J~9-bbii^lLplgTm!q}m%7_CTU_n~UI61sDgoU@VJAtU5jmU#TnIW}R}` zuiq$bcZKmhgM3w(cX7l7!v(QIlaA_TcO7t_rMo-D*rUq3ek9vgklUJv9L{T0x))Q~ zqc?Xzbh6l~bR>x0N94_b8@^h!;mbnP7w^7wX)}6$%4==2SqPr1Z~R6OsE4-0F`X8A zV^h=lSqqGM6zCoS0;JND9B2!@aBRtB^@KjCeEV303S!x=S*f6k?5RE{x(X!oE%|u$ zZp&Zr>0+UlQ);gDPV$k5n!y%)aW&=!Ux^ zYBsKUg2)~N;EaX z8X5%?Q~VgLgnzG=i6&u6DO;tAs>&qAUqUX3T$hM8JT&>!VVSRjzL%8hn?XC+z(ZgL~(kSfV09u&*L^_bXt*iqt7r7DE&M{a}FUL zQO-|k#VH)pjl(wQ6xfTFYN?>djjgu4T^%-)H~C7rCG6d|E>V*{Zp$Y=frh`KgX>S$ z23rFdeV%<6bG)*>1XceR#fIofHAt&F;XTHRU%NZAO%+xLP z2w*gM;`t;`T8NwPV^wE3f~uyC0r#-G^eJF0F@igM472U3u<&UF!tin|bXL(YBw46N zVI|s=dCS@ zUn1JRweD=zYDOq1Nxjrv}Tu3m3*96I`OI8qQjw3wkxnR)i zC{PNK5nF_ZDJw3sz`Bi%0NChCRoy04S2H}yF8RHF1NDmXyJ22OBx^TK+1ZWgwv3Hc zIjR@S%oBB0FG|h`8+SeOB}&8@`#>MpK~6+qDFikl560s4ae;4zddr7pmNmI4rXlCG z(PAvmfGy%y6iyCxD4dk*S`Nsj$kK-_{dU14v1Zt5`*d?XNkm5Ubf9qZFSI%SEf=Yv zLEb6iV?Qfl4lk;;;($c#^v*!zQ(}qjWf(@cx4N^y&7-xjrSB&e&sa>$>jq|!@Q$6p zEIiyFlT$%R`x_QH-bR&tM4uTaRB*8vQBm4to{21r=DouUG%CTsTPhls9aJ;scAkYgn@nOIq*cV{y7?YXfvwbt8kkFv=QozQgJvG?KLL4Vu zt+pbTNkQ0SNm#$w?QPWA9OLTsIfD7z=IruMjEnXjAZah7$w_wDlwd?qcW&!6UC669 zXD851@8`3YB>9GNg}xGGDf0Vd7bv4}9$E8iCXqnXSJap$a^9l#s?R8e`UZJFfw468 zYS`0zn-XWi?18Y`rv|bG8mYU+#*~+g50u%HZ-|9nwX@YRMaqFGzokT9M+~s6tA!Qj zP^~1;wg6b9q7=ZZlJv>+Po+E%DaYNh=tGH9*uUZ&WzgC+7j=g2p{i=Kk3DDatdXtd zU!Q{>;f$yM{g|Yh$A?%wnP%Q39-OTTLvBkR%0=vxzCLJ-pa!7Qt#@sFP%b;;tj%0s z^dZ}!yYLg69L5xSH@nO>r@PD2dxeNEhp|_}ER9cb56DkT^B3h_qG!=4q<@!_6G}>o z&DE}!7w(e49N*v$=|AAGf6O9(y{QKCS>kE-?++tSdaWE74myNr zfIcg6+;$SA8bRgE5bBpP9L)Szc zuQtuI&l4MHLpQ^0CpX)UeB%ExG?)ZbPAI#7;52hSQ@sU#-Kw@H4TqJ9gtO&QW7%iX znH=+zZe~vXolZ}y&IMUPkw6Y0T6oFUm2#t{?Q!>nGa_i&Eq79OAZIV;gJ#jtLg1>b z_g62X;)^W`bP740O84f$T_qXM(QBkti8p)PBxq6yob$MVJC zfb*~EG|uy2mPF2gQKF+*p@?;yj}6BsOg>7n>GGbmuX$1*Vp^Z}svz14+pQg=h@XOM z9x1UlYryUMWFxk-L#C(vk*i|Ybaxt3dCrjMWCo?fs~RG>w9fV&qP}k~k{p zc`csW;fBVR@<{%XN{Suw)E5Q`3Ett9eYW-`e_c6ao~d|jn!~+Sk`UP0Lo!(35a3q$ z;Z~{vXXxd-(K4>y2(^LSU~dk3@7@o|1F7`&g%>)r-b_ML)bw77>n~Q&7l39SMZucW zD`5nC1hqaRxV@kL8hu9dr1J_vO5J3^`$|wwv!8Z)e2pY33}iPlWe?}O-jY4Qols*U z3o=o8{KL&|{H4m?z$vCtC*pxH+hleepgrY+?rEUoS%qkIJg)d^QpU%WkDtdzMn`rl zMLxH|g{(G(Xbp_kNlMm<;mYys^dgZU0dNjtZ2F{-k}(ftJMxiO3#CFln3h9df%zoz zPNf+UC{W+gcaIEvst@95@as*i2Ufo=*lLO|EM*MS)fffnDqA-{2Xc;_&hq@6?ZVw0 z+A)E%LCp+|RmY!iH1@P$%u8jBSM;O0iI3Gpwh1df^pV?I+2e5OG14nZnHkA_+YF%> zcG?22u^)r=pEi|jOt_A?2 zRT83RCIN6t7JE9+#0=HyFTpc>@ zU0Tt*v^UtOKEaFt;Q)~pkylkD6hQTu-8|2Im{oxW!B_6W*fg}u;KpK&!Y(jibM6oK)d(M{&Fff=fyJEk- z0HrjTVi@Po51(Xz=koKQ| z{1UbFH+!B36?qSRQT`T%fywt{goRZ15ksO8keL3~kK8);3Ktqi2WUYg?}0EL`~qQ7 zse3xRnA@p1y>KvhaEH-!;3rqvKoxMJ_;J6!$c|ehAfo15ske_>b8ISt$m1 zXqu7=G#H%sK#uUYAY^>9|A0&@{hE!TzG6aw%=bY3C=l{tB8Z{xlmCFA-27!w%GTtFew+UL8e|8uXhnc7<2yVY-f_|29I1H=9s{VOQLnvf=Q z&U-cb3-CW$Nf?+ds4(3<0IV(qG)@WmK8pHpRDQ|dGEPGMJBQu{Ligl%|0i!ALHHjd zzp4J<|Hnc0FD~D2OCKUOC3)AtEy)^m8bbkUA>JWPch3<)8lfSDYY5Gk}Nreq_E7;trwz)X<+i6c`xkdmfyCp&kS8diy7j;BU)s z{GqZGkhE!(yYjbd^w8+hL!)=Uw+(>YMo(o79?~}U_%7ylT@;E*hqjpe7|%p#L|7ph zQ}=BAS)PJ{5xnO=C9l6{?#Is+;MatW72~a-4T%|I7&g-EtjsZfnLa z8u#y-xi=AaPrrHoZSAMo-~UPaRXz76m+pbK3T}f$Fiv*YF$j!ciZ`^${D2mO|DHT! z3FNR-@IN(t@4@sQ;HLD}!1xsIT?4mIC(!md1RdSnkEm+JKea}BSN`{?3#yy+p8hY@ a5c&6%*uQ>oVPIIHf0ocB6ziH_-~JCA<)J|U diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e12a8553..c583957d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Feb 10 13:49:35 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip diff --git a/gradlew b/gradlew index 91a7e269..cccdd3d5 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# 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\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=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. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then 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=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730..e95643d6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @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 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= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows 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. @@ -60,11 +59,6 @@ set _SKIP=2 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 diff --git a/rides-android/build.gradle b/rides-android/build.gradle index 0c6a3d52..9bfc7f58 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -1,177 +1,66 @@ -apply plugin: 'com.android.library' -apply plugin: 'maven' -apply plugin: 'signing' -apply plugin: 'com.github.dcendents.android-maven' -apply plugin: 'cobertura' +/* + * Copyright (C) 2017. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ buildscript { repositories { jcenter() + maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath deps.build.gradlePlugins.android } } +apply plugin: 'com.android.library' + android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion deps.build.compileSdkVersion + buildToolsVersion deps.build.buildToolsVersion defaultConfig { - minSdkVersion 14 - versionName version - consumerProguardFiles 'proguard.txt' + minSdkVersion deps.build.minSdkVersion + targetSdkVersion deps.build.targetSdkVersion + versionName VERSION_NAME + consumerProguardFiles 'consumer-proguard-rules.txt' + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } - sourceSets { - test { - java.srcDirs += '../test-shared/test/java/' - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 } } -task sourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.sourceFiles -} - -task javadoc(type: Javadoc, dependsOn: 'assembleRelease') { - classpath += files(android.getBootClasspath().join(File.pathSeparator)) - - android.libraryVariants.all { variant -> - if (variant.name == 'release') { - owner.classpath += variant.javaCompile.classpath - } - } - source = android.sourceSets.main.java.srcDirs - exclude '**/R.html', '**/R.*.html', '**/index.html' -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -artifacts { - archives sourcesJar - archives javadocJar -} - -signing { - sign configurations.archives -} - -project.archivesBaseName = artifactId - -def getReleaseRepositoryUrl() { - return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL - : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" -} - -def getSnapshotRepositoryUrl() { - return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL - : "https://oss.sonatype.org/content/repositories/snapshots/" -} - -def getRepositoryUsername() { - return hasProperty('ossrhUsername') ? ossrhUsername : "" -} - -def getRepositoryPassword() { - return hasProperty('ossrhPassword') ? ossrhPassword : "" -} - -uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - repository(url: getReleaseRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - - snapshotRepository(url: getSnapshotRepositoryUrl()) { - authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) - } - - 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' - } - - developer { - id 'tyvsmith' - name 'Ty Smith' - email 'tys@uber.com' - } - - developer { - id 'yhartanto' - name 'Yohan Hartanto' - email 'yohan@uber.com' - } - } - } - } - } -} - -cobertura { - coverageFormats = ['html', 'xml'] - coverageIgnoreTrivial = true - coverageExcludes += [ - '.*android\\.support\\.v7\\.appcompat\\.R\\.*', - '.*com\\.uber\\.sdk\\.android\\.rides\\.R\\.*', - '.*android\\.support\\.v7\\.appcompat\\.R\\$.*\\.*', - '.*com\\.uber\\.sdk\\.android\\.rides\\.R\\$.*\\.*', - '.*com\\.uber\\.sdk\\.android\\.core\\.R\\.*', - '.*com\\.uber\\.sdk\\.android\\.core\\.R\\$.*\\.*', - '.*BuildConfig.*'] -} - dependencies { compile project(':core-android') - compile 'com.android.support:appcompat-v7:23.0.1' - - testCompile 'junit:junit:4.12' - testCompile 'com.google.guava:guava:18.0' - testCompile 'com.google.http-client:google-http-client-jackson2:1.19.0' - testCompile 'org.mockito:mockito-core:1.10.19' - testCompile 'org.assertj:assertj-core:1.7.1' - testCompile 'org.robolectric:robolectric:3.2.2' - testCompile ('com.github.tomakehurst:wiremock:2.0.10-beta') { - exclude module:'asm' - } - cobertura 'com.google.android:android:4.1.1.4' + compile (deps.uber.uberRides) { + exclude module: 'slf4j-log4j12' + } + compile deps.misc.jsr305 + compile deps.support.appCompat + compile deps.support.annotations + + testCompile deps.test.junit + testCompile deps.test.assertj + testCompile deps.test.mockito + testCompile deps.test.robolectric + testCompile deps.test.guava + testCompile deps.test.wiremock } + +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/rides-android/proguard.txt b/rides-android/consumer-proguard-rules.txt similarity index 100% rename from rides-android/proguard.txt rename to rides-android/consumer-proguard-rules.txt diff --git a/rides-android/gradle.properties b/rides-android/gradle.properties index 5067c669..f0477f12 100644 --- a/rides-android/gradle.properties +++ b/rides-android/gradle.properties @@ -1 +1,21 @@ -artifactId=rides-android + +# +# Copyright (C) 2017. Uber Technologies +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +POM_NAME=Uber Core SDK (Android) +POM_ARTIFACT_ID=rides-android +POM_PACKAGING=aar +POM_DESCRIPTION=The official Android SDK for the Uber Rides API. diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java index f8e54faf..a4e29137 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java @@ -34,7 +34,7 @@ import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.android.core.utils.PackageManagers; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; @@ -46,7 +46,8 @@ */ public class RequestDeeplink implements Deeplink { - private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.1-deeplink"; + private static final String USER_AGENT_DEEPLINK = String.format("rides-android-v%s-deeplink", + BuildConfig.VERSION_NAME); @NonNull private final Uri uri; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java index 8a0b69af..3bc8c5c5 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java @@ -26,7 +26,7 @@ import android.support.annotation.NonNull; import com.uber.sdk.android.core.UberSdk; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; /** * The {@link RideRequestBehavior} to pass to the {@link RideRequestButton} to have it execute a {@link RequestDeeplink}. diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index 489eaea0..67e72ab3 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -43,8 +43,8 @@ import com.uber.sdk.android.core.auth.LoginManager; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.AccessTokenSession; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.AccessTokenSession; +import com.uber.sdk.core.client.SessionConfiguration; import java.util.Arrays; @@ -68,7 +68,8 @@ public class RideRequestActivity extends Activity implements LoginCallback, Ride static final int LOGIN_REQUEST_CODE = 1112; private static final int REQUEST_FINE_LOCATION_PERMISSION_CODE = 1002; - private static final String USER_AGENT_RIDE_WIDGET = "rides-android-v0.6.1-ride_request_widget"; + private static final String USER_AGENT_RIDE_WIDGET = String.format("rides-android-v%s-ride_request_widget", + BuildConfig.VERSION_NAME); @VisibleForTesting static final String RIDE_PARAMETERS = "ride_parameters"; static final String EXTRA_LOGIN_CONFIGURATION = "login_configuration"; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java index 872b5137..c1fac564 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java @@ -30,7 +30,7 @@ import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.core.auth.AccessTokenStorage; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; /** * The {@link RideRequestBehavior} to pass to the {@link RideRequestButton} to have it launch a {@link RideRequestActivity}. diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java index 1ba827cf..b2423975 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java @@ -42,8 +42,8 @@ import com.uber.sdk.android.core.UberStyle; import com.uber.sdk.android.rides.internal.RideRequestButtonController; import com.uber.sdk.android.rides.internal.RideRequestButtonView; -import com.uber.sdk.rides.client.Session; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.Session; +import com.uber.sdk.core.client.SessionConfiguration; import com.uber.sdk.rides.client.model.PriceEstimate; import com.uber.sdk.rides.client.model.TimeEstimate; @@ -61,7 +61,8 @@ public class RideRequestButton extends FrameLayout implements RideRequestButtonV @StyleRes int[] STYLES = {R.style.UberButton, R.style.UberButton_White}; - private static final String USER_AGENT_BUTTON = "rides-android-v0.6.1-button"; + private static final String USER_AGENT_BUTTON = String.format("rides-android-v%s-button", + BuildConfig.VERSION_NAME); private RideRequestBehavior rideRequestBehavior; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java index ecc30e44..8945ead6 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java @@ -43,8 +43,8 @@ import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; -import com.uber.sdk.rides.client.AccessTokenSession; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.AccessTokenSession; +import com.uber.sdk.core.client.SessionConfiguration; import java.util.HashMap; import java.util.Map; @@ -55,7 +55,8 @@ */ public class RideRequestView extends LinearLayout { - private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.1-ride_request_view"; + private static final String USER_AGENT_RIDE_VIEW = String.format("rides-android-v%s-ride_request_view", + BuildConfig.VERSION_NAME); @Nullable private AccessTokenSession accessTokenSession; @NonNull @VisibleForTesting RideParameters rideParameters = new RideParameters.Builder().build(); @Nullable private RideRequestViewCallback rideRequestViewCallback; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java index c4286d54..9188d83c 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java @@ -28,7 +28,7 @@ import com.uber.sdk.android.rides.RideParameters; import com.uber.sdk.android.rides.RideRequestButtonCallback; -import com.uber.sdk.rides.client.Session; +import com.uber.sdk.core.client.Session; import com.uber.sdk.rides.client.UberRidesApi; import com.uber.sdk.rides.client.error.ApiError; import com.uber.sdk.rides.client.error.ClientError; @@ -46,7 +46,7 @@ import retrofit2.Callback; import retrofit2.Response; -import static com.uber.sdk.rides.client.utils.Preconditions.checkNotNull; +import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; public class RideRequestButtonController { diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java index a1fedf9b..ea4aa78a 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java @@ -27,7 +27,7 @@ import android.content.Intent; import android.content.pm.PackageInfo; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Test; import org.robolectric.Robolectric; @@ -57,7 +57,8 @@ public class RequestDeeplinkTest extends RobolectricTestBase { private static final Double DROPOFF_LONG = -122.6789; private static final String DROPOFF_NICK = "pickupNick"; private static final String DROPOFF_ADDR = "Dropoff Address"; - private static final String USER_AGENT_DEEPLINK = "rides-android-v0.6.1-deeplink"; + private static final String USER_AGENT_DEEPLINK = String + .format("rides-android-v%s-deeplink", BuildConfig.VERSION_NAME); private Context context; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityBehaviorTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityBehaviorTest.java index c23f7322..385dd4a5 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityBehaviorTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityBehaviorTest.java @@ -25,7 +25,7 @@ import android.app.Activity; import android.content.Intent; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Test; import org.robolectric.Robolectric; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index f7a2d086..03471b2c 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -32,7 +32,7 @@ import com.uber.sdk.android.core.auth.LoginManager; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Assert; import org.junit.Before; @@ -121,13 +121,13 @@ public void onLoad_whenNullUserAgent_shouldAddRideWidgetUserAgent() { "refreshToken", "tokenType"); activity.onLoginSuccess(accessToken); - assertEquals("rides-android-v0.6.1-ride_request_widget", + assertEquals(String.format("rides-android-v%s-ride_request_widget", BuildConfig.VERSION_NAME), activity.rideRequestView.rideParameters.getUserAgent()); } @Test public void onLoad_withUserAgentInRideParametersButton_shouldNotGetOverridden() { - String userAgent = "rides-android-v0.6.1-button"; + String userAgent = String.format("rides-android-v%s-button", BuildConfig.VERSION_NAME); RideParameters rideParameters = new RideParameters.Builder().build(); rideParameters.setUserAgent(userAgent); Intent data = RideRequestActivity.newIntent(Robolectric.setupActivity(Activity.class), diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java index 72168558..b46793fa 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestViewTest.java @@ -33,8 +33,8 @@ import com.google.common.collect.ImmutableList; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.AccessTokenSession; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.AccessTokenSession; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; @@ -74,7 +74,8 @@ public class RideRequestViewTest extends RobolectricTestBase { private static final String DROPOFF_NICK = "pickupNick"; private static final String DROPOFF_ADDR = "Dropoff Address"; private static final String TOKEN_STRING = "thisIsAnAccessToken"; - private static final String USER_AGENT_RIDE_VIEW = "rides-android-v0.6.1-ride_request_view"; + private static final String USER_AGENT_RIDE_VIEW = String.format("rides-android-v%s-ride_request_view", + BuildConfig.VERSION_NAME); private AccessToken accessToken; private RideRequestView rideRequestView; @@ -144,7 +145,8 @@ public void onBuildUrl_withRideParams_shouldHaveRideParamsQueryParams() throws I @Test public void onBuildUrl_withUserAgentNonNull_shouldNotOverride() throws IOException { - String widgetUserAgent = "rides-android-v0.6.1-ride_request_widget"; + String widgetUserAgent = String.format("rides-android-v%s-ride_request_widget", + BuildConfig.VERSION_NAME); String path = "src/test/resources/riderequestviewuris/default_uri"; String expectedUri = readUriResourceWithUserAgentParam(path, widgetUserAgent); diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java b/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java index dd38ec9b..c052fb5d 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java @@ -23,8 +23,8 @@ package com.uber.sdk.android.rides; import android.support.annotation.NonNull; +import android.text.TextUtils; -import com.google.api.client.repackaged.com.google.common.base.Joiner; import com.google.common.io.Files; import java.io.File; @@ -48,7 +48,7 @@ public static String readProjectResource(String path) throws IOException { 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()))); + file = new File(TextUtils.join(File.pathSeparator, list.subList(1, list.size()))); } return Files.toString(file, StandardCharsets.UTF_8).trim(); } diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java index a1f79675..9d267741 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/internal/RideRequestButtonControllerTest.java @@ -32,7 +32,7 @@ import com.uber.sdk.android.rides.RideParameters; import com.uber.sdk.android.rides.RideRequestButtonCallback; import com.uber.sdk.rides.client.error.ApiError; -import com.uber.sdk.rides.client.internal.BigDecimalAdapter; +import com.uber.sdk.core.client.internal.BigDecimalAdapter; import com.uber.sdk.rides.client.model.PriceEstimate; import com.uber.sdk.rides.client.model.PriceEstimatesResponse; import com.uber.sdk.rides.client.model.TimeEstimate; diff --git a/samples/build.gradle b/samples/build.gradle deleted file mode 100644 index 9f777b0a..00000000 --- a/samples/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -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/login-sample/build.gradle b/samples/login-sample/build.gradle index e4edf35e..013a22d2 100644 --- a/samples/login-sample/build.gradle +++ b/samples/login-sample/build.gradle @@ -1,46 +1,51 @@ -apply plugin: 'com.android.application' - -repositories { - jcenter() - mavenLocal() -} +/* + * Copyright (c) 2017. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ buildscript { repositories { jcenter() + google() } + dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath deps.build.gradlePlugins.android } } +apply plugin: 'com.android.application' + android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion deps.build.compileSdkVersion + buildToolsVersion deps.build.buildToolsVersion defaultConfig { - applicationId "com.uber.sdk.android.login.sample" - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName "1.0" + minSdkVersion deps.build.minSdkVersion + targetSdkVersion deps.build.targetSdkVersion buildConfigField "String", "CLIENT_ID", "\"${loadSecret("UBER_CLIENT_ID")}\"" //Add your client id to gradle.properties buildConfigField "String", "REDIRECT_URI", "\"${loadSecret("UBER_REDIRECT_URI")}\"" //Add your redirect uri to gradle.properties } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt') - - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 } } - dependencies { - compile 'com.android.support:appcompat-v7:23.0.1' - compile project(':core-android') + compile project(':rides-android') + compile deps.support.appCompat } /** diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index e5a3ac17..802ac608 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -46,8 +46,8 @@ import com.uber.sdk.android.rides.samples.R; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.rides.client.Session; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.Session; +import com.uber.sdk.core.client.SessionConfiguration; import com.uber.sdk.rides.client.UberRidesApi; import com.uber.sdk.rides.client.error.ApiError; import com.uber.sdk.rides.client.error.ErrorParser; diff --git a/samples/login-sample/src/main/res/values/strings.xml b/samples/login-sample/src/main/res/values/strings.xml index b3555355..3e523233 100644 --- a/samples/login-sample/src/main/res/values/strings.xml +++ b/samples/login-sample/src/main/res/values/strings.xml @@ -21,7 +21,10 @@ ~ THE SOFTWARE. --> - + + Login Sample Copy Access Token to Clipboard Clear Access Token diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle index f1261cbe..233c84d4 100644 --- a/samples/request-button-sample/build.gradle +++ b/samples/request-button-sample/build.gradle @@ -1,45 +1,52 @@ -apply plugin: 'com.android.application' - -repositories { - jcenter() - mavenLocal() -} +/* + * Copyright (c) 2017. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ buildscript { repositories { jcenter() + google() } + dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath deps.build.gradlePlugins.android } } +apply plugin: 'com.android.application' + android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion deps.build.compileSdkVersion + buildToolsVersion deps.build.buildToolsVersion + defaultConfig { - applicationId "com.uber.sdk.android.rides.samples" - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName "1.0" + minSdkVersion deps.build.minSdkVersion + targetSdkVersion deps.build.targetSdkVersion buildConfigField "String", "CLIENT_ID", "\"${loadSecret("UBER_CLIENT_ID")}\"" //Add your client id to gradle.properties buildConfigField "String", "REDIRECT_URI", "\"${loadSecret("UBER_REDIRECT_URI")}\"" //Add your redirect uri to gradle.properties buildConfigField "String", "SERVER_TOKEN", "\"${loadSecret("UBER_SERVER_TOKEN")}\"" //Add your server token to gradle.properties } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt') - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 } } dependencies { - compile 'com.android.support:appcompat-v7:23.0.1' compile project(':rides-android') - compile project(':core-android') + compile deps.support.appCompat } /** 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 index 449b551b..f37e2962 100644 --- 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 @@ -43,8 +43,8 @@ import com.uber.sdk.android.rides.RideRequestButtonCallback; import com.uber.sdk.android.rides.RideRequestViewError; import com.uber.sdk.core.auth.AccessToken; -import com.uber.sdk.rides.client.ServerTokenSession; -import com.uber.sdk.rides.client.SessionConfiguration; +import com.uber.sdk.core.client.ServerTokenSession; +import com.uber.sdk.core.client.SessionConfiguration; import com.uber.sdk.rides.client.error.ApiError; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; diff --git a/samples/request-button-sample/src/main/res/values/strings.xml b/samples/request-button-sample/src/main/res/values/strings.xml index 30265749..97201848 100644 --- a/samples/request-button-sample/src/main/res/values/strings.xml +++ b/samples/request-button-sample/src/main/res/values/strings.xml @@ -21,7 +21,9 @@ ~ THE SOFTWARE. --> - + Uber SDK Copy Access Token to Clipboard Clear Access Token diff --git a/settings.gradle b/settings.gradle index 068c6059..939d7cc3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,4 @@ include ':rides-android' include ':core-android' -include ':samples' include ':samples:request-button-sample' include ':samples:login-sample' diff --git a/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java b/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java deleted file mode 100644 index 467445a3..00000000 --- a/test-shared/test/java/com/uber/sdk/android/core/SdkPreferences.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2016 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.core; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import com.uber.sdk.rides.client.SessionConfiguration; - -public class SdkPreferences { - - private static final String SERVER_TOKEN_KEY = "serverToken"; - private static final String SDK_PREFERENCES_NAME = "uberSdkConfig"; - private static final String SANDBOX_MODE_KEY = "sandboxMode"; - private static final String REDIRECT_URI_KEY = "redirectUri"; - private static final String REGION_KEY = "region"; - - @NonNull private SharedPreferences sharedPreferences; - - public SdkPreferences(@NonNull Context context) { - sharedPreferences = context.getSharedPreferences(SDK_PREFERENCES_NAME, Context.MODE_PRIVATE); - } - - public boolean isSandboxMode() { - return sharedPreferences.getBoolean(SANDBOX_MODE_KEY, false); - } - - @Nullable - public String getRedirectUri() { - return sharedPreferences.getString(REDIRECT_URI_KEY, null); - } - - @NonNull - public SessionConfiguration.EndpointRegion getRegion() { - return SessionConfiguration.EndpointRegion.valueOf(sharedPreferences.getString(REGION_KEY, SessionConfiguration.EndpointRegion.DEFAULT.name())); - } - - @Nullable - public String getServerToken() { - return sharedPreferences.getString(SERVER_TOKEN_KEY, null); - } - - public void setServerToken(@NonNull String serverToken) { - sharedPreferences.edit().putString(SERVER_TOKEN_KEY, serverToken).apply(); - } - - public void setSandboxMode(boolean isSandboxMode) { - sharedPreferences.edit().putBoolean(SANDBOX_MODE_KEY, isSandboxMode).apply(); - } - - public void setRedirectUri(@NonNull String redirectUri) { - sharedPreferences.edit().putString(REDIRECT_URI_KEY, redirectUri).apply(); - } - - public void setRegion(@NonNull SessionConfiguration.EndpointRegion region) { - sharedPreferences.edit().putString(REGION_KEY, region.name()).apply(); - } -} From 493572a74ba58cab3a3fb8d4ee1f282519877a4f Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 10 Nov 2017 13:22:57 -0800 Subject: [PATCH 009/165] Adding auto deploy to sonatype --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4024c3b8..5815778f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,8 +32,12 @@ script: - ./gradlew check connectedCheck --stacktrace after_success: - # - .buildscript/deploy_snapshot.sh + - .buildscript/deploy_snapshot.sh +env: + global: + - secure: "FrNGjknmIfrU2/6UHWd9toPdLcfXs07RHOgk1NdKKK0DoMTsz2ovbaftotZJ2Uk2Y2GqnTITdhd+AbwfLb/eCAymSN+FXDrZ69Yv22D5dmMmBlFNI7txb4s7cAobtOb80aZyOJpafqdxhgvDHldHereOLNl4cSWRw5behg2M+82vckDoXPmNT/h1ebGdxsV1R5t6Tkg0OEuvryytQr3OuPRC8RbuxwSefqeIe/qsTGxEkR8OWc8B+pjdt20bYt+FXezG8W371Zb72dJUYfB5tl2t/3RG5FTDGdSh7niC37MPHy/JTunUR+hTcBjBsJDUFV9aPmulKmOLHaEKP9tM5H8MGdQMyC9fIt4Qp89TytkVLoGtPXE9mDAv5oKOwqz779os7FSNQ9BswoKIYtTpsNA9KxOLlghpnBXUA8rTyCNNgjaseOw4W8aF+JsvUzdw37eVfGlIOpZiy2t+mHPZU+JpzPt0Ydr+UCOWVXL/9B7mA/B/53Y/WA5YQ3yitwdXzkSpdJ0IUePRdcUfTK4LUMUH8WgxjVKLpECGRq5dk+IXHGkFihXjzRbQiaNox+DroXzjgBqoTAZ3HgRJ3PvSWrIlzWO+eaK5G3ACeDLthPpGIGv4AUebH89hZS9s3iO/1tKG58xlZayl+19FtxkYSaR/0z3UlBDcsohJ2+L6j0w=" + - secure: "QIJXhUaNBAgAfEwyPtoh1Lrb9VhPKVFbqmmCpywTTC/hHzj9sl84ZiCraQ4W3yWg81J2CvIMWh3qy5q03BsJ6ZZ7HxBq5Z72Lm4i1ckXoZMTOPc0ppvE6Ep3bNXSG74yk6oBc+zkrNqIkYIZrbiyutZwqKJmlbmSU16Zq6E3suy47gigZnLYo/yIBHJ14slLcPJYaxwA/XuIl4ed+7oklEtSwHZ4plcIPNaeDTBaAZ5Ws4gIxteNQrVbRn8BWQf74iH/XWBj8HPsC4MQzRWi+HjNN95b/U9rAO8EGVUy1xamotRJJu8hdLF4Wynqj0MyxAQsmqmRJCISmcYXgxQ0Ig3DaR44NEfBNuxeKpR5dKF4qgtlCjSap5JArQAc/jkr0E+HtH4TPIgKdEkNm+K65Txpsw0Z5SCaURPBrsyiWK2V3Dw854qQBAw4VeeDgh63ysBjEEBsUqthRUlCgIHeHOagUPACByltzfI4Ha2Ld0/AAMx/w59+1sBgYFqiUT/ac0UHqvFkOEGbw5874rIwZrkNvwoiApTHV50AWYU5ET6UsNGrVGC8t4H792XM8hmqodxAovuyBLVvgxorLpnH76q8mnZFMlS1D5dxu+lz6p3oDchf4dl3QaMeldAx9P8+/Fky7y0sqiFgJLVQSo5nUEwjwDpt1IhSb8R2mj1Rv/A=" branches: except: From 6532f33ef5dba044b4a8163afe72aa8caf15ddb7 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 10 Nov 2017 13:40:31 -0800 Subject: [PATCH 010/165] Adding maven badge to replace updating version number --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04431734..00b22b2a 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,11 @@ To use the Uber Rides Android SDK, add the compile dependency with the latest ve ### Gradle -Add the Uber Rides Android SDK to your `build.gradle`: +[![Maven Central](https://img.shields.io/maven-central/v/com.uber.sdk/rides-android.svg)](https://mvnrepository.com/artifact/com.uber.sdk/rides-android) + ```gradle dependencies { - compile 'com.uber.sdk:rides-android:0.6.1' + compile 'com.uber.sdk:rides-android:x.y.z' } ``` From 781df7625811506b8cb907023a1d4cbe23528894 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 10 Nov 2017 14:03:52 -0800 Subject: [PATCH 011/165] Adding github templates --- .github/ISSUE_TEMPLATE.MD | 8 ++++++++ .github/PULL_REQUEST_TEMPLATE.MD | 3 +++ 2 files changed, 11 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.MD create mode 100644 .github/PULL_REQUEST_TEMPLATE.MD diff --git a/.github/ISSUE_TEMPLATE.MD b/.github/ISSUE_TEMPLATE.MD new file mode 100644 index 00000000..44997f32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.MD @@ -0,0 +1,8 @@ +Github issues are for bug reports. If this is a question around usage or understanding please use +Stack Overflow. https://stackoverflow.com/questions/tagged/uber-api + +Library version: + +Repro steps, stacktrace, screenshots: + +Expected Behavior: \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD new file mode 100644 index 00000000..9ed76f6b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -0,0 +1,3 @@ +Description: + +Related issue(s): \ No newline at end of file From 485de6ad8519cba094c5ea2c822b4e3ed27d3319 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 10 Nov 2017 16:01:18 -0800 Subject: [PATCH 012/165] Changing dev name and id to Uber --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1ffacfba..60f645a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,8 +10,8 @@ POM_LICENCE_NAME=MIT License POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php POM_LICENCE_DIST=repo -POM_DEVELOPER_ID=tyvsmith -POM_DEVELOPER_NAME=Ty Smith +POM_DEVELOPER_ID=uber +POM_DEVELOPER_NAME=Uber Technologies GITHUB_OWNER=uber GITHUB_REPO=rides-android-sdk From 05e7e98e3132051f7c53413c5099c033b8c14a1e Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 10 Nov 2017 16:31:39 -0800 Subject: [PATCH 013/165] Updating Gradle Wrapper to 4.3 --- build.gradle | 2 +- gradle/dependencies.gradle | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 54727 -> 54712 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d9a02fe5..ed67341b 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { } task wrapper(type: Wrapper) { - gradleVersion = '4.2.1' + gradleVersion = deps.build.gradleVersion distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 166932f5..fb15748a 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -21,6 +21,7 @@ def versions = [ ] def build = [ + gradleVersion: '4.3.0', buildToolsVersion: '26.0.2', compileSdkVersion: 26, ci: 'true' == System.getenv('CI'), diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 27768f1bbac3ce2d055b20d521f12da78d331e8e..ed88a042a287c140a32e1639edfc91b2a233da8c 100644 GIT binary patch delta 2187 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c583957d..f7580887 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.0-all.zip From 93a0115268fdf87025cc409eb1608c9ad3958cb0 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 10 Nov 2017 17:02:00 -0800 Subject: [PATCH 014/165] Gradle 4.3.0 was invalid and we pushed to quickly --- gradle/dependencies.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54731 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index fb15748a..ccda2f25 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -21,7 +21,7 @@ def versions = [ ] def build = [ - gradleVersion: '4.3.0', + gradleVersion: '4.3.1', buildToolsVersion: '26.0.2', compileSdkVersion: 26, ci: 'true' == System.getenv('CI'), diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ed88a042a287c140a32e1639edfc91b2a233da8c..6b6ea3ab4ff4f69d55c5fd9c0a6ac70f47d41008 100644 GIT binary patch delta 1771 zcmVlyNL@p1tGkQ%YJ9d8N?8pdv=1pol$4U>PO zAd`^I43i0P8h=nIgj*maK+>ck(4yF08T7A84n9Ntx;NiHFYg zPxObh{f?~2vSpV{Clj4Lt3A8l?tXjZKmPvpHvli=+bFhSO2r#dv|*|PTQS{%8N4Z_ zS^2mg!yInJa1(FI$J;7y#h_zeMOH;Fiq7gOA*FYulz*4ff_xYe+>YQ~sWc;4l&XR} zYsp7Z#T^y40@W&(4XaR6&^2~jzpG~|Wy8!&it@06j!7e5(90EDDA-;9_IPz`>Vx(SPj%wF;s}AzPWJPHvRz1f937N^!<0FDN+J2->pqnY^v%Op(c&MrOoXT+|D> zF{2!auj)I|NfH(uIsmsy8IZ3Wn9qmu9%1ra&WXhe) z|BRow8&>kIr*7C5rHaYSHU-|Et{7%cNCtG$cb&7t$)K-Qp^OvODbuv>`LVVq2RI&^ zy?YfB15E zIcZhwtT<;#_V)y=4oI&W_CvC^7U6#;d8nDfX1*&-xk@j!;$F~+|G+>%ODj(Q?pmj zvPZ$2K|#cD?=Tj+uJ^vXEgsbJ%R{=4i~EckDmY91>{KD~@Y*^G5Tl-x>`Vr2C4axe zda)=9G#G3;JguiMY>Gl&GMF_ORj@TV@?X}GvQ<^dh`AgminHx!>xgBV#3U){z_P`n zbyr{~zsB46=?^0=Sq3(WyEf|EB+^j%DOKf$QI=wU89iDj5r&QThr#Y?nE(P9`e{=C`ClS#gL1qX?b zsBj2FfFo_kVIn_5S>syO9QE>gmC`YKsb-boPTH)I&>lMm8K;O+xyK1wb$_zP4N!G* zoVwR=!gX?$s|2B@(y|hdy}Y81dVq&hj;o%knTJ(%J5$3dHT@pelTMh|`IKRfP$O+R zQtJ_03Do8bbb!Y*KFl3%AO)wII$7Jy$w3ESGbg7p;yU@$O;|ibC!5|i#_DxHhnAM> z64uRdjXy^+y@d7OA)4;)TYtianRI-kd?$_Kju7v0Z4(dC+i2VCm@Hva4d5~U&;(l< zVH3N!>t!Eyut0m+i9u#~hdV)m2*)2NLeRxz4&zQ#}|MbNh zr%yDQK3*$cQT&G!jg>H(9G2Z{Huq{{O6c2@4v=ZlwhP0OgZm&1C|vb(3*{Ba`dRF#?))lW~C~lg4%$ zlUvRz0x)`$ae*U~TzV3dyv`~DBz=={fg_VpeH)Wo&JdF}&l&+}lTpue0tSSWae*U~ z>CZ+1D3dVIX#y&blQEYmlU$D$lRncBlj_hK0v(c*F_$TmFVQ*zE0vQmmnoB7l@XIZ z(-4!j(Ha3LlhDy%0t=dxF_$lsVbVGR<(!jofg_W^(mMh?pObNcBaS}R001EVT{r*$ delta 1810 zcmYk6cTm&W7Jx%AYltWq1tpNMfIvi)zEqzOR;q#s0SUbeC_?ChL=hNC^y));|cg}b3oSE~-d0j2oRxOyJeP~1TihItl_Q05SK+q5K;3xuMzpa&u$K1R1^Rm{n2 zZT*4yP2TMfl;f>UN5A*yS<=r|doH(bwNqlOU#(=UKYRP@Eq06eyHcF1_eqF;qos-C zgNI$P?PODvn@X^?MRMvM4CQ`>wzp%5iS)Aej;(qt7dv!p+&TS|4^@AxbOyHV(L;#0 zUVj=dh}jh`Ca#|PqUIKeS#XzDX?23JPrJWc4va*GyLsBt9CmOtZP|zgq7pVK7XL0S zH`4j~P|ae5+t3ZY7-UG{*&gZN$O`WgarR|2Z7v6k{oLOIQS)ZhPNkd3hhF`iaKdz`S!d-J_1P1hbPFDnj{ zk~tQdQCpVlAqr5+kGvkwt?58SY;AQsT-Zft#rwFIwFQ=SUlnC8eBCN84E`WEKUf!f z6?S1a!ZB_@9v+>sDH9N9sxRxT;zPy^8W_*a+O}LWI#ij*^#oQM z12aO2=#B!oOFN;Nv2^tiJxcqI$jIC*!AR+8a zcwZaI`2Nn}C(zJTgvV&+R5Cq)LN%lq8<3B=vXU0TrxeVw!D@JpJGkY*9kiq(B zp%=l#h-GJR;Y)~0t1ZnnBI%h1&DDzfvEs4PyU()!;KC#N&P8a4rsA!-lcb^(%=Xfc z{$2p%bj;KfzE*JKCZ9{=c1X8ZmhoFlStUzc(kxS)fn3kB+`EFt zy>fd|1NTsnCn{4q_izADuuc={^5;gwJU&cuHpZJBS{f~8E~MDmM%@g6d-_6V(BpMg zR&~8_6=`6s5sbC?XY%^|3nxjx62|WP*c`T&gJAnNmFBweY%5{5#v#x(%frxmG0t_s zif+wc-vlG?0&2r(pl?_keEc5Yp++u) zu@Cr|JYo&rddSEBj+lVOQu*k|x(q&*&PV@rDPWbQ1cqhtQ-e2#;L|yL49GckU_^me zZxCRyJbuX{Ul=&SRt4{n{+~(#wIiZHHX8wE7ao)}`5!EbMTJ2>&)H)B+n8b$UnZCS zTl$qANLiqGR8;^DutsgcE1iH*t1O`3B@EOvMFB4k0=(V>5IDX9bbxzs=K)v8)Fpom aN$jA*iGw)(v(pdw2;`5Q`^A|4gZ~HOz+3bH diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f7580887..702c4b68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip From 7760e002d3d098396d4de5bae56ca5b267b415d8 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Mon, 13 Nov 2017 14:51:29 -0800 Subject: [PATCH 015/165] Resolving Checkstyle configuration and consolidating scripts between Android and Java --- build.gradle | 2 +- checkstyle.xml | 273 ++++++++++++++++++++++++++++++++++ core-android/build.gradle | 2 +- gradle/github-release.gradle | 47 +----- gradle/gradle-mvn-push.gradle | 146 +++++++++++++----- gradle/verification.gradle | 54 +++++-- rides-android/build.gradle | 2 +- 7 files changed, 428 insertions(+), 98 deletions(-) create mode 100644 checkstyle.xml diff --git a/build.gradle b/build.gradle index ed67341b..6f0bdca3 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { repositories { jcenter() - maven { url "https://plugins.gradle.org/m2/" } + maven { url deps.build.repositories.plugins } } dependencies { classpath deps.build.gradlePlugins.github diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..411815e2 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-android/build.gradle b/core-android/build.gradle index 7932f16a..0c1bdd09 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -17,7 +17,7 @@ buildscript { repositories { jcenter() - maven { url "https://plugins.gradle.org/m2/" } + maven { url deps.build.repositories.plugins } } dependencies { diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 662f1dbf..2631fc29 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -1,7 +1,6 @@ import groovy.text.GStringTemplateEngine import org.codehaus.groovy.runtime.DateGroovyMethods -apply plugin: 'distribution' apply plugin: 'net.researchgate.release' apply plugin: 'co.riiid.gradle' @@ -118,48 +117,6 @@ github { } } -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/' - include 'config' - include 'test-shared/' - include '.travis.yml' - include 'README.md' - include '.buildscript/' - } - - from('core-android') { - exclude 'build' - exclude '*.iml' - into 'core-android' - } - - from('rides-android') { - exclude 'build' - exclude '*.iml' - into 'rides-android' - } - - from('samples') { - exclude '**/build' - exclude '**/*.iml' - into 'samples' - } - } - } -} - subprojects { configure(subprojects.findAll {it.parent.name == 'samples'}) { task githubReleaseZip(type: Zip) << { @@ -201,6 +158,4 @@ subprojects { } } } - - -} \ No newline at end of file +} diff --git a/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle index 5fe4770b..186b5790 100644 --- a/gradle/gradle-mvn-push.gradle +++ b/gradle/gradle-mvn-push.gradle @@ -1,11 +1,11 @@ /* - * Copyright 2013 Chris Banes + * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -42,6 +42,101 @@ def getRepositoryPassword() { return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" } +def kotlinAndroidArtifactTasks() { + if (!project.plugins.hasPlugin('org.jetbrains.dokka-android')) { + throw new GradleException("Apply the dokka-android plugin in ${project.name}") + } + + dokka { + externalDocumentationLink { + url = new URL("http://reactivex.io/RxJava/2.x/javadoc/") + } + + outputFormat = 'html' + outputDirectory = "$buildDir/docs/kdoc" + sourceDirs = android.sourceSets.main.java.srcDirs + } + + task docJar(type: Jar, dependsOn: dokka) { + classifier = 'javadoc' + from dokka.outputDirectory + } + + task sourceJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs + } +} + +def kotlinArtifactTasks() { + if (!project.plugins.hasPlugin('org.jetbrains.dokka')) { + throw new GradleException("Apply the dokka plugin in ${project.name}") + } + + dokka { + externalDocumentationLink { + url = new URL("http://reactivex.io/RxJava/2.x/javadoc/") + } + + outputFormat = 'html' + outputDirectory = "$buildDir/docs/kdoc" + sourceDirs = sourceSets.main.allSource + } + + task docJar(type: Jar, dependsOn: dokka) { + classifier = 'javadoc' + from dokka.outputDirectory + } + + task sourceJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource + } +} + +def androidArtifactTasks() { + task androidJavadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + exclude '**/internal/*' + + if (JavaVersion.current().isJava8Compatible()) { + options.addStringOption('Xdoclint:none', '-quiet') + } + } + + task docJar(type: Jar, dependsOn: androidJavadoc) { + classifier = 'javadoc' + from androidJavadoc.destinationDir + } + + task sourceJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles + } +} + +def javaArtifactTasks() { + task javaJavadoc(type: Javadoc) { + source = sourceSets.main.allSource + + if (JavaVersion.current().isJava8Compatible()) { + options.addStringOption('Xdoclint:none', '-quiet') + } + } + + task docJar(type: Jar, dependsOn: javaJavadoc) { + classifier = 'javadoc' + from javaJavadoc.destinationDir + } + + task sourceJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource.srcDirs + } +} + afterEvaluate { project -> uploadArchives { repositories { @@ -135,19 +230,10 @@ afterEvaluate { project -> } } - task androidJavadocs(type: Javadoc) { - source = android.sourceSets.main.java.source - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - } - - task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - classifier = 'javadoc' - from androidJavadocs.destinationDir - } - - task androidSourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.source + if (project.plugins.hasPlugin('kotlin-android')) { + kotlinAndroidArtifactTasks() + } else { + androidArtifactTasks() } } else { install { @@ -186,33 +272,15 @@ afterEvaluate { project -> } } - task sourcesJar(type: Jar, dependsOn:classes) { - classifier = 'sources' - from sourceSets.main.allSource - } - - task javadocJar(type: Jar, dependsOn:javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir - } - } - - if (JavaVersion.current().isJava8Compatible()) { - allprojects { - tasks.withType(Javadoc) { - options.addStringOption('Xdoclint:none', '-quiet') - } + if (project.plugins.hasPlugin('kotlin')) { + kotlinArtifactTasks() + } else { + javaArtifactTasks() } } artifacts { - if (project.getPlugins().hasPlugin('com.android.application') || - project.getPlugins().hasPlugin('com.android.library')) { - archives androidSourcesJar - archives androidJavadocsJar - } else { - archives sourcesJar - archives javadocJar - } + archives sourceJar + archives docJar } } \ No newline at end of file diff --git a/gradle/verification.gradle b/gradle/verification.gradle index f3c2db78..a3fff45a 100644 --- a/gradle/verification.gradle +++ b/gradle/verification.gradle @@ -14,18 +14,52 @@ subprojects { apply plugin: 'checkstyle' - task checkstyleMain(type: Checkstyle, overwrite: true) { - configFile = new File("{$project.projectDir}/config/checkstyle/checkstyle-main.xml") - } + afterEvaluate { + if (project.getPlugins().hasPlugin('com.android.application') || + project.getPlugins().hasPlugin('com.android.library')) { - task checkstyleTest(type: Checkstyle, overwrite: true) { - configFile = new File("{$project.projectDir}/config/checkstyle/checkstyle-test.xml") - } + task checkstyleMain(type: Checkstyle) { + ignoreFailures = true + showViolations = true + source 'src/main', 'src/release' + include '**/*.java' + exclude '**/gen/**' + exclude '**/R.java' + exclude '**/BuildConfig.java' + reports { + xml.destination "$project.buildDir/reports/checkstyle/main.xml" + } + classpath = files() + configFile = rootProject.file('checkstyle.xml') + } - afterEvaluate { - tasks.withType(Checkstyle) { - configProperties = ['proj.module.dir' : projectDir.absolutePath, - 'checkstyle.cache.file': './build/cache/checkstyle-cache'] + task checkstyleTest(type: Checkstyle){ + ignoreFailures = true + showViolations = true + source 'src/test', 'src/androidTest' + include '**/*.java' + exclude '**/gen/**' + exclude '**/R.java' + exclude '**/BuildConfig.java' + reports { + xml.destination "$project.buildDir/reports/checkstyle/test.xml" + } + classpath = files() + configFile = rootProject.file('checkstyle.xml') + } + + task checkstyle(dependsOn:['checkstyleMain', 'checkstyleTest']){ + description 'Runs Checkstyle inspection against Android sourcesets.' + group = 'Code Quality' + } + + project.tasks.getByName("check").dependsOn "checkstyle" + } else { + checkstyle { + ignoreFailures = true + showViolations = true + configFile rootProject.file('checkstyle.xml') + } } } } \ No newline at end of file diff --git a/rides-android/build.gradle b/rides-android/build.gradle index 9bfc7f58..6d0007ef 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -17,7 +17,7 @@ buildscript { repositories { jcenter() - maven { url "https://plugins.gradle.org/m2/" } + maven { url deps.build.repositories.plugins } } dependencies { From 44cb10d8df78449db22a932ca5a42b6689c1229d Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Mon, 13 Nov 2017 15:59:52 -0800 Subject: [PATCH 016/165] Downgrading release plugin due to build task issue --- gradle/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index ccda2f25..721fc885 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -34,7 +34,7 @@ def build = [ gradlePlugins: [ android: 'com.android.tools.build:gradle:2.3.0', - release: 'net.researchgate:gradle-release:2.3.5', + release: 'net.researchgate:gradle-release:2.1.2', github: 'co.riiid:gradle-github-plugin:0.4.2', cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1', ] From 91a91d700a0b33879b21cb82a22a7941a2c6ab26 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 16 Nov 2017 14:37:33 -0800 Subject: [PATCH 017/165] Updating changelog for v0.7.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b54074..97906d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.7.0 - TBD +v0.7.0 - 11/16/2017 ------------ ### Fixed From c7c9ba85d40a6bc57af733f9535d13d3dc2e9f40 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 13:11:03 -0800 Subject: [PATCH 018/165] Preparing Release script --- CHANGELOG.md | 2 +- gradle.properties | 3 ++ gradle/github-release.gradle | 65 ++++++++++++++++-------------------- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97906d6c..83b54074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.7.0 - 11/16/2017 +v0.7.0 - TBD ------------ ### Fixed diff --git a/gradle.properties b/gradle.properties index 60f645a7..f1150966 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,7 @@ GROUP=com.uber.sdk + +#Version is managed by Gradle Release Plugin +version=0.7.0-SNAPSHOT VERSION_NAME=0.7.0-SNAPSHOT POM_URL=https://developer.uber.com diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 2631fc29..22d8f5e7 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -4,9 +4,8 @@ import org.codehaus.groovy.runtime.DateGroovyMethods apply plugin: 'net.researchgate.release' apply plugin: 'co.riiid.gradle' -ext.set("unsnapshottedVersion", VERSION_NAME.replaceAll("-SNAPSHOT", "")) +ext.set("oldVersion", VERSION_NAME.replaceAll("-SNAPSHOT", "")) ext.set("samples", project(":samples").subprojects.collect { it.path }) -ext.set("isReleaseVersion", !VERSION_NAME.endsWith("SNAPSHOT")) ["githubToken", "ossrhUsername", "ossrhPassword", "signing.keyId", "signing.password", "signing.secretKeyRingFile",].each { @@ -15,14 +14,14 @@ ext.set("isReleaseVersion", !VERSION_NAME.endsWith("SNAPSHOT")) def generateReleaseNotes() { def changelogSnippet = generateChangelogSnippet() - def model = [title : "Uber Rides Android SDK (Beta) v${unsnapshottedVersion}", + def model = [title : "Uber Rides Android SDK (Beta) v${findProperty("version")}", date : DateGroovyMethods.format(new Date(), 'MM/dd/yyyy'), snippet: changelogSnippet, assets : project.samples.collect { [ title : project(it).name, - download : GITHUB_DOWNLOAD_PREFIX + "v${unsnapshottedVersion}/" - + project(it).name + "-v${unsnapshottedVersion}.zip", + download : GITHUB_DOWNLOAD_PREFIX + "v${findProperty("version")}/" + + project(it).name + "-v${findProperty("version")}.zip", description: project(it).description, ] }] @@ -53,46 +52,37 @@ def checkAndDefaultProperty(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) - } -} +task updateReleaseVersionChangelog() << { + def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') + def changelog = rootProject.file('CHANGELOG.md') + def changelogText = changelog.text + def date = new Date().format('MM/dd/yyyy') -gradle.taskGraph.afterTask { Task task, TaskState state -> - println task.path - if (task.path.endsWith("release") || task.path.endsWith("githubReleaseZip") - || task.path.endsWith("publicrepoDistZip")) { - checkForChangelogUpdates(task) - } -} + if (changelogText.startsWith("v${oldVersion} - TBD")) { + def updatedChangelog = changelogText.replace("v${oldVersion} - TBD", + "v${newVersion} - ${date}") + changelog.write(updatedChangelog) -// 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 } } -task updateChangelog() << { +task updateNewVersionChangelog() << { def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') def changelog = rootProject.file('CHANGELOG.md') def changelogText = changelog.text - if (!changelogText.startsWith("v${newVersion} -")) { + + if (!changelogText.startsWith("v${newVersion} - TBD")) { def updatedChangelog = "v${newVersion} - TBD\n" def dashesCount = updatedChangelog.length()-1 - updatedChangelog += "-"*dashesCount + "\n\n" - - changelog.write(updatedChangelog + changelogText) + updatedChangelog += "-"*dashesCount + "\n\n" + changlogText + changelog.write(updatedChangelog) } } afterReleaseBuild.dependsOn(":core-android:uploadArchives", ":rides-android:uploadArchives") updateVersion.dependsOn ":githubRelease" -commitNewVersion.dependsOn ':updateChangelog' +preTagCommit.dependsOn ':updateReleaseVersionChangelog' +commitNewVersion.dependsOn ':updateNewVersionChangelog' githubRelease.dependsOn project(":samples").subprojects.collect { it.path + ":githubReleaseZip" } release { @@ -100,37 +90,38 @@ release { failOnPublishNeeded = false failOnSnapshotDependencies = false revertOnFail = true - tagTemplate = "v${unsnapshottedVersion}" + tagTemplate = 'v$version' + versionProperties = ['VERSION_NAME'] } github { owner = GITHUB_OWNER repo = GITHUB_REPO token = "${githubToken}" - tagName = "v${unsnapshottedVersion}" + tagName = "v${findProperty("version")}" targetCommitish = GITHUB_BRANCH - name = "v${unsnapshottedVersion}" + name = "v${findProperty("version")}" body = generateReleaseNotes() assets = project.samples.collect { project(it).buildDir.absolutePath + "/distributions/" + project(it).name + - "-v${unsnapshottedVersion}.zip" + "-v${findProperty("version")}.zip" } } subprojects { configure(subprojects.findAll {it.parent.name == 'samples'}) { task githubReleaseZip(type: Zip) << { - version = "v${unsnapshottedVersion}" + version = "v${findProperty("version")}" from('.') { filesNotMatching("**/*.png") { filter { String line -> line.replaceAll("compile project\\(':rides-android'\\)", - "compile '${groupId}:rides-android:${unsnapshottedVersion}'") + "compile '${groupId}:rides-android:${findProperty("version")}'") } filter { String line -> line.replaceAll("compile project\\(':core-android'\\)", - "compile '${groupId}:core-android:${unsnapshottedVersion}'") + "compile '${groupId}:core-android:${findProperty("version")}'") } } into '.' From 42b94a3a01fc34999c147f569ef085efa0a9e28b Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 15:02:32 -0800 Subject: [PATCH 019/165] fixing travis build for signing --- gradle/github-release.gradle | 30 ++++++++++++++++++++---------- gradle/gradle-mvn-push.gradle | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 22d8f5e7..790c2f49 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -7,11 +7,28 @@ apply plugin: 'co.riiid.gradle' ext.set("oldVersion", VERSION_NAME.replaceAll("-SNAPSHOT", "")) ext.set("samples", project(":samples").subprojects.collect { it.path }) -["githubToken", "ossrhUsername", "ossrhPassword", - "signing.keyId", "signing.password", "signing.secretKeyRingFile",].each { +["GITHUB_TOKEN"].each { checkAndDefaultProperty(it) } +def checkAndDefaultProperty(prop) { + if (!project.hasProperty(prop)) { + checkProperty(prop) + rootProject.ext.set(prop, prop) + } +} + +def checkProperty(prop) { + if (!project.hasProperty(prop)) { + def message = "Add " + prop + " to your ~/.gradle/gradle.properties file." + if (isReleaseBuild()) { + throw new GradleException(message) + } else { + logger.warn(message) + } + } +} + def generateReleaseNotes() { def changelogSnippet = generateChangelogSnippet() def model = [title : "Uber Rides Android SDK (Beta) v${findProperty("version")}", @@ -45,13 +62,6 @@ def generateChangelogSnippet() { 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) - } -} - task updateReleaseVersionChangelog() << { def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') def changelog = rootProject.file('CHANGELOG.md') @@ -97,7 +107,7 @@ release { github { owner = GITHUB_OWNER repo = GITHUB_REPO - token = "${githubToken}" + token = "${GITHUB_TOKEN}" tagName = "v${findProperty("version")}" targetCommitish = GITHUB_BRANCH name = "v${findProperty("version")}" diff --git a/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle index 186b5790..ca2d8130 100644 --- a/gradle/gradle-mvn-push.gradle +++ b/gradle/gradle-mvn-push.gradle @@ -20,6 +20,32 @@ apply plugin: 'signing' version = VERSION_NAME group = GROUP +["SONATYPE_NEXUS_USERNAME", "SONATYPE_NEXUS_PASSWORD"].each { + checkAndDefaultProperty(it) +} + +["signing.keyId", "signing.password", "signing.secretKeyRingFile"].each { + checkProperty(it) +} + +def checkAndDefaultProperty(prop) { + if (!project.hasProperty(prop)) { + checkProperty(prop) + rootProject.ext.set(prop, prop) + } +} + +def checkProperty(prop) { + if (!project.hasProperty(prop)) { + def message = "Add " + prop + " to your ~/.gradle/gradle.properties file." + if (isReleaseBuild()) { + throw new GradleException(message) + } else { + logger.warn(message) + } + } +} + def isReleaseBuild() { return VERSION_NAME.contains("SNAPSHOT") == false } From 3df797c05cf8dbe510320a506501539676b4fbaf Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 15:16:01 -0800 Subject: [PATCH 020/165] Fixing permission on snapshot deploy script --- .buildscript/deploy_snapshot.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .buildscript/deploy_snapshot.sh diff --git a/.buildscript/deploy_snapshot.sh b/.buildscript/deploy_snapshot.sh old mode 100644 new mode 100755 From 6f93d1a9bf5cbd35add08445e20dece39d4befd7 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 15:20:04 -0800 Subject: [PATCH 021/165] Fixing release script --- gradle/github-release.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 790c2f49..4366cb0f 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -29,6 +29,10 @@ def checkProperty(prop) { } } +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + def generateReleaseNotes() { def changelogSnippet = generateChangelogSnippet() def model = [title : "Uber Rides Android SDK (Beta) v${findProperty("version")}", From 625d7cd98574eb7101cf857eb1cb8767d6ed38d5 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 15:38:42 -0800 Subject: [PATCH 022/165] [Gradle Release Plugin] - pre tag commit: 'v0.7.0'. --- CHANGELOG.md | 2 +- gradle.properties | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b54074..7660beb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.7.0 - TBD +v0.7.0 - 11/17/2017 ------------ ### Fixed diff --git a/gradle.properties b/gradle.properties index f1150966..33b0bf70 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,17 @@ +#Fri, 17 Nov 2017 15:37:20 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.7.0-SNAPSHOT -VERSION_NAME=0.7.0-SNAPSHOT -POM_URL=https://developer.uber.com +version=0.7.0 +VERSION_NAME=0.7.0 +POM_URL=https\://developer.uber.com -POM_SCM_URL=https://github.com/uber/rides-android-sdk/ -POM_SCM_CONNECTION=scm:git:git://github.com/uber/rides-android-sdk.git -POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/uber/rides-android-sdk.git +POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ +POM_SCM_CONNECTION=scm\:git\:git\://github.com/uber/rides-android-sdk.git +POM_SCM_DEV_CONNECTION=scm\:git\:ssh\://git@github.com/uber/rides-android-sdk.git POM_LICENCE_NAME=MIT License -POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php +POM_LICENCE_URL=http\://www.opensource.org/licenses/mit-license.php POM_LICENCE_DIST=repo POM_DEVELOPER_ID=uber @@ -18,5 +19,5 @@ POM_DEVELOPER_NAME=Uber Technologies GITHUB_OWNER=uber GITHUB_REPO=rides-android-sdk -GITHUB_DOWNLOAD_PREFIX=https://github.com/uber/rides-android-sdk/releases/download/ +GITHUB_DOWNLOAD_PREFIX=https\://github.com/uber/rides-android-sdk/releases/download/ GITHUB_BRANCH=master From 73ca27f1c5ffeaaac1bd728813245abd113cba1b Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 15:44:11 -0800 Subject: [PATCH 023/165] [Gradle Release Plugin] - new version commit: v0.8.0. --- CHANGELOG.md | 3 +++ gradle.properties | 4 ++-- gradle/github-release.gradle | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7660beb4..62a70970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.8.0 - TBD +------------ + v0.7.0 - 11/17/2017 ------------ diff --git a/gradle.properties b/gradle.properties index 33b0bf70..e3604358 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.7.0 -VERSION_NAME=0.7.0 +version=0.8.0-SNAPSHOT +VERSION_NAME=0.8.0-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 4366cb0f..0dfc8d3e 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -88,7 +88,7 @@ task updateNewVersionChangelog() << { if (!changelogText.startsWith("v${newVersion} - TBD")) { def updatedChangelog = "v${newVersion} - TBD\n" def dashesCount = updatedChangelog.length()-1 - updatedChangelog += "-"*dashesCount + "\n\n" + changlogText + updatedChangelog += "-"*dashesCount + "\n\n" + changelogText changelog.write(updatedChangelog) } } From f4149e25e1476161f94b4d9ae138917a1d54a873 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 16:02:14 -0800 Subject: [PATCH 024/165] Refinements to release script based on 0.7 feedback --- gradle/github-release.gradle | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 0dfc8d3e..f7e73184 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -35,14 +35,14 @@ def isReleaseBuild() { def generateReleaseNotes() { def changelogSnippet = generateChangelogSnippet() - def model = [title : "Uber Rides Android SDK (Beta) v${findProperty("version")}", + def model = [title : "Uber Rides Android SDK (Beta) v${rootProject.version}", date : DateGroovyMethods.format(new Date(), 'MM/dd/yyyy'), snippet: changelogSnippet, assets : project.samples.collect { [ title : project(it).name, - download : GITHUB_DOWNLOAD_PREFIX + "v${findProperty("version")}/" - + project(it).name + "-v${findProperty("version")}.zip", + download : GITHUB_DOWNLOAD_PREFIX + "v${rootProject.version}/" + + project(it).name + "-v${rootProject.version}.zip", description: project(it).description, ] }] @@ -66,8 +66,12 @@ def generateChangelogSnippet() { return " " + snippet.trim() } +task test2 << { + rootProject.version="0.9.0-SNAPSHOT" +} + task updateReleaseVersionChangelog() << { - def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') + def newVersion = rootProject.version.replaceAll('-SNAPSHOT', '') def changelog = rootProject.file('CHANGELOG.md') def changelogText = changelog.text def date = new Date().format('MM/dd/yyyy') @@ -81,7 +85,7 @@ task updateReleaseVersionChangelog() << { } task updateNewVersionChangelog() << { - def newVersion = findProperty("version").replaceAll('-SNAPSHOT', '') + def newVersion = rootProject.version.replaceAll('-SNAPSHOT', '') def changelog = rootProject.file('CHANGELOG.md') def changelogText = changelog.text @@ -112,30 +116,30 @@ github { owner = GITHUB_OWNER repo = GITHUB_REPO token = "${GITHUB_TOKEN}" - tagName = "v${findProperty("version")}" + tagName = "v${rootProject.version}" targetCommitish = GITHUB_BRANCH - name = "v${findProperty("version")}" + name = "v${rootProject.version}" body = generateReleaseNotes() assets = project.samples.collect { project(it).buildDir.absolutePath + "/distributions/" + project(it).name + - "-v${findProperty("version")}.zip" + "-v${rootProject.version}.zip" } } subprojects { configure(subprojects.findAll {it.parent.name == 'samples'}) { task githubReleaseZip(type: Zip) << { - version = "v${findProperty("version")}" + version = "v${rootProject.version}" from('.') { filesNotMatching("**/*.png") { filter { String line -> line.replaceAll("compile project\\(':rides-android'\\)", - "compile '${groupId}:rides-android:${findProperty("version")}'") + "compile '${groupId}:rides-android:${rootProject.version}'") } filter { String line -> line.replaceAll("compile project\\(':core-android'\\)", - "compile '${groupId}:core-android:${findProperty("version")}'") + "compile '${groupId}:core-android:${rootProject.version}'") } } into '.' From 12509f2c46402c75c318aeb763b8468c89be1731 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 16:04:05 -0800 Subject: [PATCH 025/165] removing test task --- gradle/github-release.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index f7e73184..b4851402 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -66,10 +66,6 @@ def generateChangelogSnippet() { return " " + snippet.trim() } -task test2 << { - rootProject.version="0.9.0-SNAPSHOT" -} - task updateReleaseVersionChangelog() << { def newVersion = rootProject.version.replaceAll('-SNAPSHOT', '') def changelog = rootProject.file('CHANGELOG.md') From e7aec6c5307af651fa0818e73ef9d8799c7ca444 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 17 Nov 2017 16:14:21 -0800 Subject: [PATCH 026/165] Fixing errors on CI for release builds --- gradle/github-release.gradle | 7 +------ gradle/gradle-mvn-push.gradle | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index b4851402..d5d7efee 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -20,12 +20,7 @@ def checkAndDefaultProperty(prop) { def checkProperty(prop) { if (!project.hasProperty(prop)) { - def message = "Add " + prop + " to your ~/.gradle/gradle.properties file." - if (isReleaseBuild()) { - throw new GradleException(message) - } else { - logger.warn(message) - } + logger.warn("Add " + prop + " to your ~/.gradle/gradle.properties file.") } } diff --git a/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle index ca2d8130..48bd47c8 100644 --- a/gradle/gradle-mvn-push.gradle +++ b/gradle/gradle-mvn-push.gradle @@ -37,12 +37,7 @@ def checkAndDefaultProperty(prop) { def checkProperty(prop) { if (!project.hasProperty(prop)) { - def message = "Add " + prop + " to your ~/.gradle/gradle.properties file." - if (isReleaseBuild()) { - throw new GradleException(message) - } else { - logger.warn(message) - } + logger.warn("Add " + prop + " to your ~/.gradle/gradle.properties file.") } } From 2c77e3b2dbb1e60e8da1eab692b836210e5e96b7 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 28 Nov 2017 14:44:45 -0800 Subject: [PATCH 027/165] Migrate uses of AccessTokenMigration to AccessTokenStorage --- CHANGELOG.md | 4 +- README.md | 10 ++-- .../sdk/android/core/auth/LoginButton.java | 35 +++++++++---- .../sdk/android/core/auth/LoginManager.java | 50 ++++++++++++------- .../android/core/auth/LoginButtonTest.java | 9 ++-- .../android/core/auth/LoginManagerTest.java | 33 ++++++------ .../android/rides/RideRequestActivity.java | 16 +++--- .../rides/RideRequestActivityBehavior.java | 3 +- .../rides/RideRequestActivityTest.java | 5 +- .../android/samples/LoginSampleActivity.java | 18 +++---- .../android/rides/samples/SampleActivity.java | 7 +-- 11 files changed, 111 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a70970..e8f1ffab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ v0.8.0 - TBD ------------ +### Changed + - [Issue #101](https://github.com/uber/rides-android-sdk/issues/101) LoginManager now uses AccessTokenStorage v0.7.0 - 11/17/2017 ------------ @@ -96,7 +98,7 @@ The RideRequestButton has been updated to show information about your Uber ride. - Moved core functionality and authentication related classes to `core-android` and the Java SDK. Imports require updating. - Removed `UberSdk.initialize(context, clientId)` and all `UberSdk` setters in favor of `UberSdk.initialize(sessionConfiguration)` -- Removed `LoginManager.loginWithScopes(activity, scopes)` in favor of `LoginManager.login(activity)` after using `new LoginManager(accessTokenManager, callback)` +- Removed `LoginManager.loginWithScopes(activity, scopes)` in favor of `LoginManager.login(activity)` after using `new LoginManager(accessTokenStorage, callback)` - Removed `AccessTokenManager.getAccessToken(key)` and `AccessTokenManager.setAccessToken(key, token)` in favor of `new AccessTokenManager(context, key)` - Removed `LoginManager.onActivityResult(requestCode, resultCode, data, callback)` in favor of `LoginManager.onActivityResult(activity, requestCode, resultCode, data)` diff --git a/README.md b/README.md index 00b22b2a..5135edd7 100644 --- a/README.md +++ b/README.md @@ -184,8 +184,8 @@ LoginCallback loginCallback = new LoginCallback() { // Successful login! The AccessToken will have already been saved. } } -AccessTokenManager accessTokenManager = new AccessTokenManager(context); -LoginManager loginManager = new LoginManager(accessTokenManager, loginCallback); +AccessTokenStorage accessTokenStorage = new AccessTokenManager(context); +LoginManager loginManager = new LoginManager(accessTokenStorage, loginCallback); loginManager.login(activity); ``` @@ -209,20 +209,20 @@ Upon a failure to login, an `AuthenticationError` will be provided in the `Login If your app allows users to authorize via your own customized logic, you will need to create an `AccessToken` manually and save it in shared preferences using the `AccessTokenManager`. ```java -AccessTokenManager accessTokenManager = new AccessTokenManager(context); +AccessTokenStorage accessTokenStorage = new AccessTokenManager(context); Date expirationTime = 2592000; List scopes = Arrays.asList(Scope.RIDE_WIDGETS); String token = "obtainedAccessToken"; String refreshToken = "obtainedRefreshToken"; String tokenType = "obtainedTokenType"; AccessToken accessToken = new AccessToken(expirationTime, scopes, token, refreshToken, tokenType); -accessTokenManager.setAccessToken(accessToken); +accessTokenStorage.setAccessToken(accessToken); ``` The `AccessTokenManager` can also be used to get an access token or delete it. ```java accessTokenManger.getAccessToken(); -accessTokenManager.removeAccessToken(); +accessTokenStorage.removeAccessToken(); ``` To keep track of multiple users, create an AccessTokenManager for each AccessToken. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java index e5097144..ee38ad6c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java @@ -38,12 +38,12 @@ import com.uber.sdk.android.core.UberButton; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.UberStyle; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; import java.util.Collection; -import static com.uber.sdk.android.core.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; /** @@ -55,7 +55,7 @@ public class LoginButton extends UberButton { int[] STYLES = {R.style.UberButton_Login, R.style.UberButton_Login_White}; - private AccessTokenManager accessTokenManager; + private AccessTokenStorage accessTokenStorage; private SessionConfiguration sessionConfiguration; private LoginManager loginManager; private LoginCallback callback; @@ -164,13 +164,26 @@ public LoginButton setRequestCode(int requestCode) { } /** - * Optionally provide {@link AccessTokenManager}. If none provided, one will be created. + * Optionally provide {@link AccessTokenStorage}. If none provided, one will be created. * - * @param accessTokenManager + * @param accessTokenStorage * @return this instance of {@link LoginButton} */ - public LoginButton setAccessTokenManager(AccessTokenManager accessTokenManager) { - this.accessTokenManager = accessTokenManager; + public LoginButton setAccessTokenStorage(AccessTokenStorage accessTokenStorage) { + this.accessTokenStorage = accessTokenStorage; + return this; + } + + /** + * Optionally provide {@link AccessTokenStorage}. If none provided, one will be created. + * + * @param accessTokenStorage + * @return this instance of {@link LoginButton} + * @deprecated use {@link LoginButton#setAccessTokenStorage(AccessTokenStorage)} + */ + @Deprecated + public LoginButton setAccessTokenManager(AccessTokenStorage accessTokenStorage) { + this.accessTokenStorage = accessTokenStorage; return this; } @@ -228,7 +241,7 @@ public void onActivityResult( @VisibleForTesting protected synchronized LoginManager getOrCreateLoginManager() { if (loginManager == null) { - loginManager = new LoginManager(getOrCreateAccessTokenManager(), + loginManager = new LoginManager(getOrCreateAccessTokenStorage(), callback, getOrCreateSessionConfiguration(), requestCode); @@ -237,11 +250,11 @@ protected synchronized LoginManager getOrCreateLoginManager() { } @NonNull - protected synchronized AccessTokenManager getOrCreateAccessTokenManager() { - if (accessTokenManager == null) { - accessTokenManager = new AccessTokenManager(getContext()); + protected synchronized AccessTokenStorage getOrCreateAccessTokenStorage() { + if (accessTokenStorage == null) { + accessTokenStorage = new AccessTokenManager(getContext()); } - return accessTokenManager; + return accessTokenStorage; } @NonNull diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 52290f95..d06b4f36 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -33,6 +33,7 @@ import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.AccessTokenSession; import com.uber.sdk.core.client.ServerTokenSession; @@ -88,7 +89,7 @@ public class LoginManager { private static final String USER_AGENT = String.format("core-android-v%s-login_manager", BuildConfig.VERSION_NAME); - private final AccessTokenManager accessTokenManager; + private final AccessTokenStorage accessTokenStorage; private final LoginCallback callback; private final SessionConfiguration sessionConfiguration; private final int requestCode; @@ -96,40 +97,40 @@ public class LoginManager { private boolean redirectForAuthorizationCode = false; /** - * @param accessTokenManager to store access token. + * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} * is called. */ public LoginManager( - @NonNull AccessTokenManager accessTokenManager, + @NonNull AccessTokenStorage accessTokenStorage, @NonNull LoginCallback loginCallback) { - this(accessTokenManager, loginCallback, UberSdk.getDefaultSessionConfiguration()); + this(accessTokenStorage, loginCallback, UberSdk.getDefaultSessionConfiguration()); } /** - * @param accessTokenManager to store access token. + * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. * @param configuration to provide authentication information */ public LoginManager( - @NonNull AccessTokenManager accessTokenManager, + @NonNull AccessTokenStorage accessTokenStorage, @NonNull LoginCallback loginCallback, @NonNull SessionConfiguration configuration) { - this(accessTokenManager, loginCallback, configuration, REQUEST_CODE_LOGIN_DEFAULT); + this(accessTokenStorage, loginCallback, configuration, REQUEST_CODE_LOGIN_DEFAULT); } /** - * @param accessTokenManager to store access token. + * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. * @param configuration to provide authentication information * @param requestCode custom code to use for Activity communication */ public LoginManager( - @NonNull AccessTokenManager accessTokenManager, + @NonNull AccessTokenStorage accessTokenStorage, @NonNull LoginCallback loginCallback, @NonNull SessionConfiguration configuration, int requestCode) { - this.accessTokenManager = accessTokenManager; + this.accessTokenStorage = accessTokenStorage; this.callback = loginCallback; this.sessionConfiguration = configuration; this.requestCode = requestCode; @@ -185,11 +186,21 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { } /** - * @return {@link AccessTokenManager} that is used. + * @return {@link AccessTokenStorage} that is used. + * @deprecated Use {@link LoginManager#getAccessTokenStorage()} */ + @Deprecated @NonNull - public AccessTokenManager getAccessTokenManager() { - return accessTokenManager; + public AccessTokenStorage getAccessTokenManager() { + return accessTokenStorage; + } + + /** + * @return {@link AccessTokenStorage} that is used. + */ + @NonNull + public AccessTokenStorage getAccessTokenStorage() { + return accessTokenStorage; } /** @@ -205,7 +216,7 @@ public SessionConfiguration getSessionConfiguration() { } /** - * Gets session based on current {@link SessionConfiguration} and {@link AccessTokenManager}. + * Gets session based on current {@link SessionConfiguration} and {@link AccessTokenStorage}. * * @return Session to use with API requests. * @throws IllegalStateException when not logged in @@ -214,20 +225,21 @@ public SessionConfiguration getSessionConfiguration() { public Session getSession() { if (sessionConfiguration.getServerToken() != null) { return new ServerTokenSession(sessionConfiguration); - } else if (accessTokenManager.getAccessToken() != null) { - return new AccessTokenSession(sessionConfiguration, accessTokenManager); + } else if (accessTokenStorage.getAccessToken() != null) { + return new AccessTokenSession(sessionConfiguration, accessTokenStorage); } else { throw new IllegalStateException("Tried to call getSession but not logged in or server token set."); } } /** - * Determines if the Login Manager is authenticated based on set {@link SessionConfiguration} and {@link AccessTokenManager} + * Determines if the Login Manager is authenticated based on set {@link SessionConfiguration} + * and {@link AccessTokenStorage} * * @return true if authenticated, otherwise false; */ public boolean isAuthenticated() { - return (sessionConfiguration.getServerToken() != null || accessTokenManager.getAccessToken() != null); + return (sessionConfiguration.getServerToken() != null || accessTokenStorage.getAccessToken() != null); } /** @@ -344,7 +356,7 @@ private void handleResultOk(@Nullable Intent data) { callback.onAuthorizationCodeReceived(authorizationCode); } else { AccessToken accessToken = AuthUtils.createAccessToken(data); - accessTokenManager.setAccessToken(accessToken); + accessTokenStorage.setAccessToken(accessToken); callback.onLoginSuccess(accessToken); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index 8fb22202..08fbd45f 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -31,6 +31,7 @@ import com.google.common.collect.Sets; import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; @@ -61,7 +62,7 @@ public class LoginButtonTest extends RobolectricTestBase { LoginCallback loginCallback; @Mock - AccessTokenManager accessTokenManager; + AccessTokenStorage accessTokenStorage; private LoginButton loginButton; @@ -133,12 +134,12 @@ public void testButtonClickWithoutLoginManager_shouldCreateNew() { loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()); loginButton.setCallback(loginCallback); loginButton.setScopes(SCOPES); - loginButton.setAccessTokenManager(accessTokenManager); + loginButton.setAccessTokenStorage(accessTokenStorage); loginButton.callOnClick(); assertThat(loginButton.getLoginManager()).isNotNull(); - assertThat(loginButton.getLoginManager().getAccessTokenManager()) - .isEqualTo(accessTokenManager); + assertThat(loginButton.getLoginManager().getAccessTokenStorage()) + .isEqualTo(accessTokenStorage); } @Test diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 44f994a1..5d0e53f4 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -35,6 +35,7 @@ import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenAuthenticator; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; @@ -110,7 +111,7 @@ public class LoginManagerTest extends RobolectricTestBase { LoginCallback callback; @Mock - AccessTokenManager accessTokenManager; + AccessTokenStorage accessTokenStorage; SessionConfiguration sessionConfiguration; @@ -119,7 +120,7 @@ public class LoginManagerTest extends RobolectricTestBase { @Before public void setup() { sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID).setScopes(MIXED_SCOPES).build(); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); when(activity.getPackageManager()).thenReturn(packageManager); when(activity.getApplicationInfo()).thenReturn(new ApplicationInfo()); @@ -142,7 +143,7 @@ public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { @Test public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchIntent() { - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration, REQUEST_CODE); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); @@ -160,7 +161,7 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte @Test public void loginWithoutAppInstalledGeneralScopes_shouldLaunchWebView() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); @@ -184,7 +185,7 @@ public void loginWithoutAppInstalledPrivilegedScopes_shouldLaunchAppInstall() { when(activity.getPackageManager()).thenReturn(packageManager); sessionConfiguration = sessionConfiguration.newBuilder().build(); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration) + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration) .setRedirectForAuthorizationCode(false); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); @@ -211,7 +212,7 @@ public void onActivityResult_whenResultOkAndHasToken_shouldCallbackSuccess() { ArgumentCaptor storedToken = ArgumentCaptor.forClass(AccessToken.class); ArgumentCaptor returnedToken = ArgumentCaptor.forClass(AccessToken.class); - verify(accessTokenManager).setAccessToken(storedToken.capture()); + verify(accessTokenStorage).setAccessToken(storedToken.capture()); verify(callback).onLoginSuccess(returnedToken.capture()); assertThat(storedToken.getValue()).isEqualTo(ACCESS_TOKEN); @@ -298,7 +299,7 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAut public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_shouldError() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); sessionConfiguration = sessionConfiguration.newBuilder().build(); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration) + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration) .setRedirectForAuthorizationCode(false); loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); @@ -309,7 +310,7 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_should @Test public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImplicitGrant() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); @@ -331,41 +332,41 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImp @Test public void isAuthenticated_withServerToken_true() { - when(accessTokenManager.getAccessToken()).thenReturn(null); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration.newBuilder().setServerToken("serverToken").build()); + when(accessTokenStorage.getAccessToken()).thenReturn(null); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration.newBuilder().setServerToken("serverToken").build()); assertTrue(loginManager.isAuthenticated()); } @Test public void isAuthenticated_withAccessToken_true() { - when(accessTokenManager.getAccessToken()).thenReturn(ACCESS_TOKEN); + when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); assertTrue(loginManager.isAuthenticated()); } @Test public void isAuthenticated_withoutAccessOrServerToken_false() { - when(accessTokenManager.getAccessToken()).thenReturn(null); + when(accessTokenStorage.getAccessToken()).thenReturn(null); assertFalse(loginManager.isAuthenticated()); } @Test public void getSession_withServerToken_successful() { - when(accessTokenManager.getAccessToken()).thenReturn(null); - loginManager = new LoginManager(accessTokenManager, callback, sessionConfiguration.newBuilder().setServerToken("serverToken").build()); + when(accessTokenStorage.getAccessToken()).thenReturn(null); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration.newBuilder().setServerToken("serverToken").build()); Session session = loginManager.getSession(); assertEquals("serverToken", session.getAuthenticator().getSessionConfiguration().getServerToken()); } @Test public void getSession_withAccessToken_successful() { - when(accessTokenManager.getAccessToken()).thenReturn(ACCESS_TOKEN); + when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); Session session = loginManager.getSession(); assertEquals(ACCESS_TOKEN, ((AccessTokenAuthenticator)session.getAuthenticator()).getTokenStorage().getAccessToken()); } @Test(expected = IllegalStateException.class) public void getSession_withoutAccessTokenOrToken_fails() { - when(accessTokenManager.getAccessToken()).thenReturn(null); + when(accessTokenStorage.getAccessToken()).thenReturn(null); loginManager.getSession(); } diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index 67e72ab3..dacfc289 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -42,6 +42,7 @@ import com.uber.sdk.android.core.auth.LoginCallback; import com.uber.sdk.android.core.auth.LoginManager; import com.uber.sdk.core.auth.AccessToken; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.AccessTokenSession; import com.uber.sdk.core.client.SessionConfiguration; @@ -75,7 +76,7 @@ public class RideRequestActivity extends Activity implements LoginCallback, Ride static final String EXTRA_LOGIN_CONFIGURATION = "login_configuration"; static final String EXTRA_ACCESS_TOKEN_STORAGE_KEY = "access_token_storage_key"; - @VisibleForTesting AccessTokenManager accessTokenManager; + @VisibleForTesting AccessTokenStorage accessTokenStorage; @Nullable @VisibleForTesting AlertDialog authenticationErrorDialog; @Nullable @VisibleForTesting AlertDialog rideRequestErrorDialog; @VisibleForTesting RideRequestView rideRequestView; @@ -119,7 +120,7 @@ public void onCreate(Bundle savedInstanceState) { .getString(EXTRA_ACCESS_TOKEN_STORAGE_KEY, AccessTokenManager.ACCESS_TOKEN_DEFAULT_KEY); rideRequestView = (RideRequestView) findViewById(R.id.ub__ride_request_view); - accessTokenManager = new AccessTokenManager(this, accessTokenStorageKey); + accessTokenStorage = new AccessTokenManager(this, accessTokenStorageKey); RideParameters rideParameters = getIntent().getParcelableExtra(RIDE_PARAMETERS); if (rideParameters == null) { @@ -136,7 +137,7 @@ public void onCreate(Bundle savedInstanceState) { .setScopes(Arrays.asList(Scope.RIDE_WIDGETS)) .build(); - loginManager = new LoginManager(accessTokenManager, this, sessionConfiguration, LOGIN_REQUEST_CODE); + loginManager = new LoginManager(accessTokenStorage, this, sessionConfiguration, LOGIN_REQUEST_CODE); rideRequestView.setRideParameters(rideParameters); rideRequestView.setRideRequestViewCallback(this); @@ -174,7 +175,7 @@ public void onErrorReceived(@NonNull RideRequestViewError error) { break; case NO_ACCESS_TOKEN: case UNAUTHORIZED: - accessTokenManager.removeAccessToken(); + accessTokenStorage.removeAccessToken(); login(); break; default: @@ -212,7 +213,7 @@ public void onLoginError(@NonNull AuthenticationError error) { @Override public void onLoginSuccess(@NonNull AccessToken accessToken) { - accessTokenManager.setAccessToken(accessToken); + accessTokenStorage.setAccessToken(accessToken); load(); } @@ -238,10 +239,11 @@ public void onRequestPermissionsResult( * Loads the appropriate view in the activity based on whether user is successfully authorized or not. */ private void load() { - AccessToken accessToken = accessTokenManager.getAccessToken(); + AccessToken accessToken = accessTokenStorage.getAccessToken(); if (accessToken != null) { - AccessTokenSession session = new AccessTokenSession(sessionConfiguration, accessTokenManager); + AccessTokenSession session = new AccessTokenSession(sessionConfiguration, + accessTokenStorage); rideRequestView.setSession(session); loadRideRequestView(); diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java index c1fac564..025bd84e 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java @@ -29,7 +29,6 @@ import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.auth.AccessTokenManager; -import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.client.SessionConfiguration; /** @@ -71,7 +70,7 @@ public RideRequestActivityBehavior(@NonNull Activity activity, * @param activity the {@link Activity} to launch the {@link RideRequestActivity} from * @param requestCode the request code to use for the {@link Activity} result * @param loginConfiguration used for login scenarios from ride request screen - * @param accessTokenStorageKey key to use for looking in {@link AccessTokenStorage} + * @param accessTokenStorageKey key to use for looking in {@link com.uber.sdk.core.auth.AccessTokenStorage} */ public RideRequestActivityBehavior(@NonNull Activity activity, int requestCode, diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index 03471b2c..19d7a58d 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -31,6 +31,7 @@ import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.core.auth.LoginManager; import com.uber.sdk.core.auth.AccessToken; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; @@ -192,12 +193,12 @@ public void onLoad_whenLoginCanceled_shouldReturnCanceledResultIntent() { @Test public void onLoad_whenRideRequestViewAuthorizationErrorOccurs_shouldAttemptLoginLoad() { activity.loginManager = mock(LoginManager.class); - activity.accessTokenManager = mock(AccessTokenManager.class); + activity.accessTokenStorage = mock(AccessTokenStorage.class); ShadowActivity shadowActivity = shadowOf(activity); activity.onErrorReceived(RideRequestViewError.UNAUTHORIZED); assertNull(shadowActivity.getResultIntent()); - verify(activity.accessTokenManager, times(1)).removeAccessToken(); + verify(activity.accessTokenStorage, times(1)).removeAccessToken(); verify(activity.loginManager).login(refEq(activity)); } diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 802ac608..1123de90 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -36,7 +36,6 @@ import android.widget.Button; import android.widget.Toast; -import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.core.auth.LoginButton; @@ -45,6 +44,7 @@ import com.uber.sdk.android.rides.samples.BuildConfig; import com.uber.sdk.android.rides.samples.R; import com.uber.sdk.core.auth.AccessToken; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; @@ -82,7 +82,7 @@ public class LoginSampleActivity extends AppCompatActivity { private LoginButton blackButton; private LoginButton whiteButton; private Button customButton; - private AccessTokenManager accessTokenManager; + private AccessTokenStorage accessTokenStorage; private LoginManager loginManager; private SessionConfiguration configuration; @@ -99,24 +99,24 @@ protected void onCreate(Bundle savedInstanceState) { validateConfiguration(configuration); - accessTokenManager = new AccessTokenManager(this); + accessTokenStorage = new AccessTokenManager(this); //Create a button with a custom request code whiteButton = (LoginButton) findViewById(R.id.uber_button_white); whiteButton.setCallback(new SampleLoginCallback()) .setSessionConfiguration(configuration); - //Create a button using a custom AccessTokenManager + //Create a button using a custom AccessTokenStorage //Custom Scopes are set using XML for this button as well in R.layout.activity_sample blackButton = (LoginButton) findViewById(R.id.uber_button_black); - blackButton.setAccessTokenManager(accessTokenManager) + blackButton.setAccessTokenStorage(accessTokenStorage) .setCallback(new SampleLoginCallback()) .setSessionConfiguration(configuration) .setRequestCode(LOGIN_BUTTON_CUSTOM_REQUEST_CODE); //Use a custom button with an onClickListener to call the LoginManager directly - loginManager = new LoginManager(accessTokenManager, + loginManager = new LoginManager(accessTokenStorage, new SampleLoginCallback(), configuration, CUSTOM_BUTTON_REQUEST_CODE); @@ -215,15 +215,15 @@ public boolean onOptionsItemSelected(MenuItem item) { // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); - accessTokenManager = new AccessTokenManager(this); + accessTokenStorage = new AccessTokenManager(this); //noinspection SimplifiableIfStatement if (id == R.id.action_clear) { - accessTokenManager.removeAccessToken(); + accessTokenStorage.removeAccessToken(); Toast.makeText(this, "AccessToken cleared", Toast.LENGTH_SHORT).show(); return true; } else if (id == R.id.action_copy) { - AccessToken accessToken = accessTokenManager.getAccessToken(); + AccessToken accessToken = accessTokenStorage.getAccessToken(); String message = accessToken == null ? "No AccessToken stored" : "AccessToken copied to clipboard"; if (accessToken != null) { 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 index f37e2962..5c9bdc69 100644 --- 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 @@ -43,6 +43,7 @@ import com.uber.sdk.android.rides.RideRequestButtonCallback; import com.uber.sdk.android.rides.RideRequestViewError; import com.uber.sdk.core.auth.AccessToken; +import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.client.ServerTokenSession; import com.uber.sdk.core.client.SessionConfiguration; import com.uber.sdk.rides.client.error.ApiError; @@ -165,15 +166,15 @@ public boolean onOptionsItemSelected(MenuItem item) { // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); - AccessTokenManager accessTokenManager = new AccessTokenManager(this); + AccessTokenStorage accessTokenStorage = new AccessTokenManager(this); //noinspection SimplifiableIfStatement if (id == R.id.action_clear) { - accessTokenManager.removeAccessToken(); + accessTokenStorage.removeAccessToken(); Toast.makeText(this, "AccessToken cleared", Toast.LENGTH_SHORT).show(); return true; } else if (id == R.id.action_copy) { - AccessToken accessToken = accessTokenManager.getAccessToken(); + AccessToken accessToken = accessTokenStorage.getAccessToken(); String message = accessToken == null ? "No AccessToken stored" : "AccessToken copied to clipboard"; if (accessToken != null) { From 502a188e369e1c3bde66a97e9d9b862e7543e54e Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 28 Nov 2017 14:55:02 -0800 Subject: [PATCH 028/165] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f1ffab..a5fb2bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,7 +98,7 @@ The RideRequestButton has been updated to show information about your Uber ride. - Moved core functionality and authentication related classes to `core-android` and the Java SDK. Imports require updating. - Removed `UberSdk.initialize(context, clientId)` and all `UberSdk` setters in favor of `UberSdk.initialize(sessionConfiguration)` -- Removed `LoginManager.loginWithScopes(activity, scopes)` in favor of `LoginManager.login(activity)` after using `new LoginManager(accessTokenStorage, callback)` +- Removed `LoginManager.loginWithScopes(activity, scopes)` in favor of `LoginManager.login(activity)` after using `new LoginManager(accessTokenManager, callback)` - Removed `AccessTokenManager.getAccessToken(key)` and `AccessTokenManager.setAccessToken(key, token)` in favor of `new AccessTokenManager(context, key)` - Removed `LoginManager.onActivityResult(requestCode, resultCode, data, callback)` in favor of `LoginManager.onActivityResult(activity, requestCode, resultCode, data)` From 66c6fae1666b1905ca03e313ed8a360e31b314af Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 28 Nov 2017 16:36:11 -0800 Subject: [PATCH 029/165] Fix Deprecation for AccessTokenManager method --- .../java/com/uber/sdk/android/core/auth/LoginManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index d06b4f36..af0e669e 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -191,8 +191,9 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { */ @Deprecated @NonNull - public AccessTokenStorage getAccessTokenManager() { - return accessTokenStorage; + @SuppressWarnings("unchecked") + public T getAccessTokenManager() { + return (T) accessTokenStorage; } /** From 4555ca3264f2d1b862fcb6a526de5503cd57d198 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Wed, 6 Dec 2017 17:10:53 -0800 Subject: [PATCH 030/165] First commit using chrometabs --- CHANGELOG.md | 5 + README.md | 32 +++ core-android/build.gradle | 1 + .../uber/sdk/android/core/auth/AuthUtils.java | 96 ++++++++- .../android/core/auth/CustomTabsHelper.java | 190 ++++++++++++++++++ .../sdk/android/core/auth/LoginActivity.java | 54 +---- .../sdk/android/core/auth/LoginManager.java | 84 +++++++- .../uber/sdk/android/core/utils/Utility.java | 13 ++ .../sdk/android/core/auth/AuthUtilsTest.java | 20 +- gradle/dependencies.gradle | 3 +- .../login-sample/src/main/AndroidManifest.xml | 8 + .../android/samples/LoginSampleActivity.java | 6 + .../src/main/AndroidManifest.xml | 7 + 13 files changed, 449 insertions(+), 70 deletions(-) create mode 100644 core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a5fb2bef..ac965f29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ v0.8.0 - TBD ------------ + ### Changed - [Issue #101](https://github.com/uber/rides-android-sdk/issues/101) LoginManager now uses AccessTokenStorage +### Added + - [Issue #22](https://github.com/uber/rides-android-sdk/issues/22) Customtab support + + v0.7.0 - 11/17/2017 ------------ diff --git a/README.md b/README.md index 5135edd7..d26176a2 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ To get the hash of your signing certificate, run this command with the alias of keytool -exportcert -alias -keystore | openssl sha1 -binary | openssl base64 ``` + Before you can request any rides, you need to get an `AccessToken`. The Uber Rides SDK provides the `LoginManager` class for this task. Simply create a new instance and use its login method to present the login screen to the user. ```java @@ -199,6 +200,37 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data){ } ``` +#### Authentication Migration (Version 0.8 and above) + +We are moving the startActivityForResult/onActivityResult contract to use the standard URI +contract indicated in the [IETF RFC](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12). + +Now, you must additionally specify your redirect_uri in the Android Manifest and +proxy that information to the Uber SDK. The recommended format is com.example.yourpackage://redirect-uri + +To handle migrations from older Uber apps using the old contract, we recommend you implement both +approaches in the +short term until we indicate otherwise. + +```xml + + + + + +``` + +Following this change, you must proxy the results to the `LoginManager` from the called activity. + +```java +@Override +protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + loginManager.handleAuthorizationResult(intent); +} +``` + The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if that is unavailable, fallback to Implicit Grant if privileged scopes are not requested, otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager.setRedirectForAuthorizationCode(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). #### Login Errors diff --git a/core-android/build.gradle b/core-android/build.gradle index 0c1bdd09..955dbf24 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -49,6 +49,7 @@ dependencies { compile (deps.uber.uberCore) { exclude module: 'slf4j-log4j12' } + compile deps.support.chrometabs compile deps.misc.jsr305 compile deps.support.appCompat compile deps.support.annotations diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index 6ff243c8..b7e539b1 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -22,17 +22,25 @@ package com.uber.sdk.android.core.auth; +import android.app.Activity; import android.content.Intent; +import android.content.pm.ResolveInfo; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Base64; +import android.webkit.WebView; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; +import com.uber.sdk.core.client.SessionConfiguration; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Set; /** @@ -118,6 +126,22 @@ static Collection stringCollectionToScopeCollection(@NonNull Collection resolvedActivityList = activity.getPackageManager() + .queryIntentActivities(activityIntent, 0); + + boolean supported = false; + for (ResolveInfo resolveInfo : resolvedActivityList) { + if (resolveInfo.activityInfo.packageName.equals(activity.getPackageName())) { + supported = true; + } + } + return supported; + } + /** * Converts a {@link Collection} of {@link Scope}s into a space-delimited {@link String}. * @@ -150,7 +174,29 @@ public static String mergeScopeStrings(String... scopes) { } @NonNull - static Intent parseTokenUri(@NonNull Uri uri) throws LoginAuthenticationException { + static AccessToken parseTokenUri(@NonNull Uri uri) throws LoginAuthenticationException { + final long expiresIn; + try { + expiresIn = Long.valueOf(uri.getQueryParameter(KEY_EXPIRATION_TIME)); + } catch (NumberFormatException ex) { + throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE); + } + + final String accessToken = uri.getQueryParameter(KEY_TOKEN); + final String refreshToken = uri.getQueryParameter(KEY_REFRESH_TOKEN); + final String scope = uri.getQueryParameter(KEY_SCOPES); + final String tokenType = uri.getQueryParameter(KEY_TOKEN_TYPE); + + if (TextUtils.isEmpty(accessToken) || TextUtils.isEmpty(scope) || TextUtils.isEmpty(tokenType)) { + throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE); + } + + return new AccessToken(expiresIn, AuthUtils.stringToScopeCollection + (scope), accessToken, refreshToken, tokenType); + } + + @NonNull + static Intent parseTokenUriToIntent(@NonNull Uri uri) throws LoginAuthenticationException { final long expiresIn; try { expiresIn = Long.valueOf(uri.getQueryParameter(KEY_EXPIRATION_TIME)); @@ -201,4 +247,52 @@ static AccessToken createAccessToken(Intent intent) { static String createEncodedParam(String rawParam) { return Base64.encodeToString(rawParam.getBytes(), Base64.DEFAULT); } + + /** + * Builds a URL {@link String} using the necessary parameters to load in the {@link WebView}. + * + * @return the URL to load in the {@link WebView} + */ + @NonNull + static String buildUrl( + @NonNull String redirectUri, + @NonNull ResponseType responseType, + @NonNull SessionConfiguration configuration) { + + final String CLIENT_ID_PARAM = "client_id"; + final String ENDPOINT = "login"; + final String HTTPS = "https"; + final String PATH = "oauth/v2/authorize"; + final String REDIRECT_PARAM = "redirect_uri"; + final String RESPONSE_TYPE_PARAM = "response_type"; + final String SCOPE_PARAM = "scope"; + final String SHOW_FB_PARAM = "show_fb"; + final String SIGNUP_PARAMS = "signup_params"; + final String REDIRECT_LOGIN = "{\"redirect_to_login\":true}"; + + + + Uri.Builder builder = new Uri.Builder(); + builder.scheme(HTTPS) + .authority(ENDPOINT + "." + configuration.getEndpointRegion().getDomain()) + .appendEncodedPath(PATH) + .appendQueryParameter(CLIENT_ID_PARAM, configuration.getClientId()) + .appendQueryParameter(REDIRECT_PARAM, redirectUri) + .appendQueryParameter(RESPONSE_TYPE_PARAM, responseType.toString().toLowerCase( + Locale.US)) + .appendQueryParameter(SCOPE_PARAM, getScopes(configuration)) + .appendQueryParameter(SHOW_FB_PARAM, "false") + .appendQueryParameter(SIGNUP_PARAMS, AuthUtils.createEncodedParam(REDIRECT_LOGIN)); + + return builder.build().toString(); + } + + private static String getScopes(SessionConfiguration configuration) { + String scopes = AuthUtils.scopeCollectionToString(configuration.getScopes()); + if (!configuration.getCustomScopes().isEmpty()) { + scopes = AuthUtils.mergeScopeStrings(scopes, + AuthUtils.customScopeCollectionToString(configuration.getCustomScopes())); + } + return scopes; + } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java new file mode 100644 index 00000000..631a192e --- /dev/null +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java @@ -0,0 +1,190 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.uber.sdk.android.core.auth; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.customtabs.CustomTabsIntent; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for Custom Tabs. + */ +public class CustomTabsHelper { + private static final String TAG = "CustomTabsHelper"; + static final String STABLE_PACKAGE = "com.android.chrome"; + static final String BETA_PACKAGE = "com.chrome.beta"; + static final String DEV_PACKAGE = "com.chrome.dev"; + static final String LOCAL_PACKAGE = "com.google.android.apps.chrome"; + private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE = + "android.support.customtabs.extra.KEEP_ALIVE"; + private static final String ACTION_CUSTOM_TABS_CONNECTION = + "android.support.customtabs.action.CustomTabsService"; + + private static String packageNameToUse; + + private CustomTabsHelper() {} + + /** + * Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView. + * + * @param activity The host activity. + * @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available. + * @param uri the Uri to be opened. + * @param fallback a CustomTabFallback to be used if Custom Tabs is not available. + */ + public static void openCustomTab(Activity activity, + CustomTabsIntent customTabsIntent, + Uri uri, + CustomTabFallback fallback) { + String packageName = getPackageNameToUse(activity); + + if (packageName == null) { + if (fallback != null) { + fallback.openUri(activity, uri); + } + } else { + customTabsIntent.intent.setPackage(packageName); + customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + customTabsIntent.launchUrl(activity, uri); + } + } + + /** + * Goes through all apps that handle VIEW intents and have a warmup service. Picks + * the one chosen by the user if there is one, otherwise makes a best effort to return a + * valid package name. + * + * This is not threadsafe. + * + * @param context {@link Context} to use for accessing {@link PackageManager}. + * @return The package name recommended to use for connecting to custom tabs related components. + */ + @Nullable + public static String getPackageNameToUse(Context context) { + if (packageNameToUse != null) return packageNameToUse; + + PackageManager pm = context.getPackageManager(); + // Get default VIEW intent handler. + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); + ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); + String defaultViewHandlerPackageName = null; + if (defaultViewHandlerInfo != null) { + defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName; + } + + // Get all apps that can handle VIEW intents. + List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + List packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION); + serviceIntent.setPackage(info.activityInfo.packageName); + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add(info.activityInfo.packageName); + } + } + + // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents + // and service calls. + if (packagesSupportingCustomTabs.isEmpty()) { + packageNameToUse = null; + } else if (packagesSupportingCustomTabs.size() == 1) { + packageNameToUse = packagesSupportingCustomTabs.get(0); + } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) + && !hasSpecializedHandlerIntents(context, activityIntent) + && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) { + packageNameToUse = defaultViewHandlerPackageName; + } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { + packageNameToUse = STABLE_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { + packageNameToUse = BETA_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { + packageNameToUse = DEV_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { + packageNameToUse = LOCAL_PACKAGE; + } + return packageNameToUse; + } + + /** + * Used to check whether there is a specialized handler for a given intent. + * @param intent The intent to check with. + * @return Whether there is a specialized handler for the given intent. + */ + private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) { + try { + PackageManager pm = context.getPackageManager(); + List handlers = pm.queryIntentActivities( + intent, + PackageManager.GET_RESOLVED_FILTER); + if (handlers == null || handlers.size() == 0) { + return false; + } + for (ResolveInfo resolveInfo : handlers) { + IntentFilter filter = resolveInfo.filter; + if (filter == null) continue; + if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue; + if (resolveInfo.activityInfo == null) continue; + return true; + } + } catch (RuntimeException e) { + Log.e(TAG, "Runtime exception while getting specialized handlers"); + } + return false; + } + + /** + * @return All possible chrome package names that provide custom tabs feature. + */ + public static String[] getPackages() { + return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE}; + } + + /** + * Fallback that uses browser + */ + static class BrowserFallback implements CustomTabFallback { + @Override + public void openUri(Activity activity, Uri uri) { + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.startActivity(intent); + } + } + + /** + * To be used as a fallback to open the Uri when Custom Tabs is not available. + */ + interface CustomTabFallback { + /** + * + * @param activity The Activity that wants to open the Uri. + * @param uri The uri to be opened by the fallback. + */ + void openUri(Activity activity, Uri uri); + } +} \ No newline at end of file diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index a4ee7d98..2d86caa0 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -39,8 +39,6 @@ import com.uber.sdk.android.core.R; import com.uber.sdk.core.client.SessionConfiguration; -import java.util.Locale; - /** * {@link android.app.Activity} that shows web view for Uber user authentication and authorization. */ @@ -108,7 +106,7 @@ protected void onCreate(Bundle savedInstanceState) { webView.getSettings().setAppCacheEnabled(true); webView.getSettings().setDomStorageEnabled(true); webView.setWebViewClient(createOAuthClient(redirectUri)); - webView.loadUrl(buildUrl(redirectUri, responseType, sessionConfiguration)); + webView.loadUrl(AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration)); } protected OAuthWebViewClient createOAuthClient(String redirectUri) { @@ -128,7 +126,7 @@ void onError(@NonNull AuthenticationError error) { void onTokenReceived(@NonNull Uri uri) { try { - Intent data = AuthUtils.parseTokenUri(uri); + Intent data = AuthUtils.parseTokenUriToIntent(uri); setResult(RESULT_OK, data); finish(); @@ -150,54 +148,6 @@ void onCodeReceived(Uri uri) { } } - /** - * Builds a URL {@link String} using the necessary parameters to load in the {@link WebView}. - * - * @return the URL to load in the {@link WebView} - */ - @NonNull - @VisibleForTesting - String buildUrl( - @NonNull String redirectUri, - @NonNull ResponseType responseType, - @NonNull SessionConfiguration configuration) { - - final String CLIENT_ID_PARAM = "client_id"; - final String ENDPOINT = "login"; - final String HTTPS = "https"; - final String PATH = "oauth/v2/authorize"; - final String REDIRECT_PARAM = "redirect_uri"; - final String RESPONSE_TYPE_PARAM = "response_type"; - final String SCOPE_PARAM = "scope"; - final String SHOW_FB_PARAM = "show_fb"; - final String SIGNUP_PARAMS = "signup_params"; - final String REDIRECT_LOGIN = "{\"redirect_to_login\":true}"; - - - - Uri.Builder builder = new Uri.Builder(); - builder.scheme(HTTPS) - .authority(ENDPOINT + "." + configuration.getEndpointRegion().getDomain()) - .appendEncodedPath(PATH) - .appendQueryParameter(CLIENT_ID_PARAM, configuration.getClientId()) - .appendQueryParameter(REDIRECT_PARAM, redirectUri) - .appendQueryParameter(RESPONSE_TYPE_PARAM, responseType.toString().toLowerCase(Locale.US)) - .appendQueryParameter(SCOPE_PARAM, getScopes(configuration)) - .appendQueryParameter(SHOW_FB_PARAM, "false") - .appendQueryParameter(SIGNUP_PARAMS, AuthUtils.createEncodedParam(REDIRECT_LOGIN)); - - return builder.build().toString(); - } - - private String getScopes(SessionConfiguration configuration) { - String scopes = AuthUtils.scopeCollectionToString(configuration.getScopes()); - if (!configuration.getCustomScopes().isEmpty()) { - scopes = AuthUtils.mergeScopeStrings(scopes, - AuthUtils.customScopeCollectionToString(configuration.getCustomScopes())); - } - return scopes; - } - /** * Custom {@link WebViewClient} for authorization. */ diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index af0e669e..d8cac4c4 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -24,14 +24,18 @@ import android.app.Activity; import android.content.Intent; +import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.customtabs.CustomTabsIntent; +import android.text.TextUtils; import android.util.Log; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; +import com.uber.sdk.android.core.utils.Utility; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; @@ -54,6 +58,11 @@ public class LoginManager { */ static final String EXTRA_ERROR = "ERROR"; + /** + * Used to retrieve the {@link AuthenticationError} from a result URI + */ + static final String QUERY_PARAM_ERROR = "error"; + /** * Used to retrieve the Access Token from an {@link Intent}. */ @@ -145,6 +154,8 @@ public void login(@NonNull Activity activity) { checkNotEmpty(sessionConfiguration.getScopes(), "Scopes must be set in the Session " + "Configuration."); + validateRedirectUriRegistration(activity); + SsoDeeplink ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(sessionConfiguration.getClientId()) .scopes(sessionConfiguration.getScopes()) @@ -169,9 +180,7 @@ public void login(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForImplicitGrant(@NonNull Activity activity) { - - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN); - activity.startActivityForResult(intent, requestCode); + loginWithCustomtab(activity, ResponseType.TOKEN); } /** @@ -180,9 +189,7 @@ public void loginForImplicitGrant(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForAuthorizationCode(@NonNull Activity activity) { - - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.CODE); - activity.startActivityForResult(intent, requestCode); + loginWithCustomtab(activity, ResponseType.CODE); } /** @@ -304,6 +311,58 @@ public void onActivityResult( } } + private void loginWithCustomtab(@NonNull final Activity activity, @NonNull ResponseType responseType) { + + final String url = AuthUtils.buildUrl(sessionConfiguration.getRedirectUri(), responseType, + sessionConfiguration); + + if (AuthUtils.isRedirectUriRegistered(activity, + Uri.parse(sessionConfiguration.getRedirectUri()))) { + final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); + + CustomTabsHelper.openCustomTab(activity, intent, Uri.parse(url), + new CustomTabsHelper.BrowserFallback()); + } else { + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, responseType); + activity.startActivityForResult(intent, requestCode); + } + } + + /** + * Handle the Uber authorization result. + * This will parse the Intent to pull the access token or error out of the Data URI, and call + * the set callback. + * + * @param data + */ + public void handleAuthorizationResult(@NonNull Intent data) { + if (data.getData() != null + && data.getData().toString().startsWith(sessionConfiguration.getRedirectUri())) { + + final String fragment = data.getData().getFragment(); + + if (fragment == null) { + callback.onLoginError(AuthenticationError.INVALID_RESPONSE); + return; + } + + final Uri fragmentUri = new Uri.Builder().encodedQuery(fragment).build(); + + final String error = fragmentUri.getQueryParameter(QUERY_PARAM_ERROR); + if (!TextUtils.isEmpty(error)) { + callback.onLoginError(AuthenticationError.fromString(error)); + return; + } + + try { + AccessToken token = AuthUtils.parseTokenUri(fragmentUri); + callback.onLoginSuccess(token); + } catch (LoginAuthenticationException e) { + callback.onLoginError(e.getAuthenticationError()); + } + } + } + private void handleResultCancelled( @NonNull Activity activity, @Nullable Intent data) {// An error occurred during login @@ -363,4 +422,17 @@ private void handleResultOk(@Nullable Intent data) { } } + + private void validateRedirectUriRegistration(@NonNull Activity activity) { + if (!AuthUtils.isRedirectUriRegistered(activity, Uri.parse(sessionConfiguration + .getRedirectUri()))) { + + String error = "Must now register redirect_uri in AndroidManifest.xml. See README."; + if (Utility.isDebugable(activity)) { + throw new IllegalStateException(error); + } else { + Log.e("Uber SDK", error); + } + } + } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java index f9e5996d..25d58c4c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java @@ -1,5 +1,9 @@ package com.uber.sdk.android.core.utils; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.support.annotation.NonNull; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -15,6 +19,15 @@ public static String sha1hash(byte[] bytes) { return hashWithAlgorithm(HASH_ALGORITHM_SHA1, bytes); } + /** + * Detects if the Application is currently in a Debug state + */ + public static boolean isDebugable(@NonNull Context context) { + return ( 0 != ( context.getApplicationInfo().flags & ApplicationInfo + .FLAG_DEBUGGABLE ) ); + + } + private static String hashWithAlgorithm(String algorithm, String key) { return hashWithAlgorithm(algorithm, key.getBytes()); } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index a261e186..69d0bc17 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -117,7 +117,7 @@ public void scopeCollectionToString_whenEmptyScopeCollection_shouldReturnEmptySt public void generateAccessTokenFromUrl_whenNullFragment_shouldThrowInvalidResponseError() { String redirectUri = "http://localhost:1234/"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUri)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUri)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -128,7 +128,7 @@ public void generateAccessTokenFromUrl_whenNullFragment_shouldThrowInvalidRespon public void generateAccessTokenFromUrl_whenValidErrorInQueryParameter_shouldThrowAuthenticationError() { String redirectUri = "http://localhost:1234?error=mismatching_redirect_uri"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUri)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUri)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -139,7 +139,7 @@ public void generateAccessTokenFromUrl_whenValidErrorInQueryParameter_shouldThro public void generateAccessTokenFromUrl_whenInvalidErrorInQueryParameter_shouldThrowAuthenticationError() { String redirectUri = "http://localhost:1234?error=bogus_error"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUri)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUri)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -150,7 +150,7 @@ public void generateAccessTokenFromUrl_whenInvalidErrorInQueryParameter_shouldTh public void generateAccessTokenFromUrl_whenNoToken_shouldThrowAuthenticationError() { String redirectUrl = "http://localhost:1234?expires_in=" + EXPIRATION_TIME + "&scope=history"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUrl)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -161,7 +161,7 @@ public void generateAccessTokenFromUrl_whenNoToken_shouldThrowAuthenticationErro public void generateAccessTokenFromUrl_whenNoExpirationTime_shouldThrowAuthenticationError() { String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&scope=history"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUrl)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -173,7 +173,7 @@ public void generateAccessTokenFromUrl_whenNoScopes_shouldThrowAuthenticationErr String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&expires_in=" + EXPIRATION_TIME; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUrl)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -185,7 +185,7 @@ public void generateAccessTokenFromUrl_whenBadExpirationTime_shouldThrowAuthenti String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&expires_in=notALong" + "&scope=history"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUrl)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -198,7 +198,7 @@ public void generateAccessTokenFromUrl_whenBadScopes_shouldThrowAuthenticationEr EXPIRATION_TIME + "&scope=history notAScopeAtAll"; try { - AuthUtils.parseTokenUri(Uri.parse(redirectUrl)); + AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl)); fail("Should throw an exception"); } catch (LoginAuthenticationException e) { assertEquals(AuthenticationError.INVALID_RESPONSE, e.getAuthenticationError()); @@ -211,7 +211,7 @@ public void generateAccessTokenFromUrl_whenValidAccessTokenWithOneScope_shouldGe String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&expires_in=" + EXPIRATION_TIME + "&scope=history" + "&token_type=" + BEARER; - AccessToken accessToken = AuthUtils.createAccessToken(AuthUtils.parseTokenUri(Uri.parse(redirectUrl))); + AccessToken accessToken = AuthUtils.createAccessToken(AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl))); assertNotNull(accessToken); assertEquals(accessToken.getToken(), ACCESS_TOKEN_STRING); assertEquals(accessToken.getScopes().size(), 1); @@ -225,7 +225,7 @@ public void generateAccessTokenFromUrl_whenValidAccessTokenWithMultipleScopes_sh String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&expires_in=" + EXPIRATION_TIME + "&scope=history profile" + "&token_type=" + BEARER; - AccessToken accessToken = AuthUtils.createAccessToken(AuthUtils.parseTokenUri(Uri.parse(redirectUrl))); + AccessToken accessToken = AuthUtils.createAccessToken(AuthUtils.parseTokenUriToIntent(Uri.parse(redirectUrl))); assertNotNull(accessToken); assertEquals(accessToken.getToken(), ACCESS_TOKEN_STRING); assertEquals(accessToken.getScopes().size(), 2); diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 721fc885..9444905f 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -25,7 +25,7 @@ def build = [ buildToolsVersion: '26.0.2', compileSdkVersion: 26, ci: 'true' == System.getenv('CI'), - minSdkVersion: 14, + minSdkVersion: 15, targetSdkVersion: 26, repositories: [ @@ -47,6 +47,7 @@ def misc = [ def support = [ annotations: "com.android.support:support-annotations:${versions.support}", appCompat: "com.android.support:appcompat-v7:${versions.support}", + chrometabs: "com.android.support:customtabs:${versions.support}", ] def test = [ diff --git a/samples/login-sample/src/main/AndroidManifest.xml b/samples/login-sample/src/main/AndroidManifest.xml index 8949fe57..ff38adaa 100644 --- a/samples/login-sample/src/main/AndroidManifest.xml +++ b/samples/login-sample/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ --> + + + + + + diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 1123de90..6a42fc0a 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -138,6 +138,12 @@ protected void onResume() { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + loginManager.handleAuthorizationResult(intent); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(LOG_TAG, String.format("onActivityResult requestCode:[%s] resultCode [%s]", diff --git a/samples/request-button-sample/src/main/AndroidManifest.xml b/samples/request-button-sample/src/main/AndroidManifest.xml index 3bb1dfa4..6ea3b5bf 100644 --- a/samples/request-button-sample/src/main/AndroidManifest.xml +++ b/samples/request-button-sample/src/main/AndroidManifest.xml @@ -38,6 +38,13 @@ + + + + + + From 7b86262cbe9041209a2f4b48f170325c5f7c8d53 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 9 Jan 2018 16:50:21 -0800 Subject: [PATCH 031/165] Fixing tests and sample apps for partial customtab impl --- .../sdk/android/core/auth/LoginManager.java | 59 +++++++++++++------ .../android/core/auth/LoginButtonTest.java | 3 +- .../android/core/auth/LoginManagerTest.java | 4 +- .../core/auth/OAuthWebViewClientTest.java | 17 ------ .../android/rides/RideRequestActivity.java | 3 +- .../android/samples/LoginSampleActivity.java | 1 + 6 files changed, 48 insertions(+), 39 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index d8cac4c4..9c45809c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -180,7 +180,15 @@ public void login(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForImplicitGrant(@NonNull Activity activity) { - loginWithCustomtab(activity, ResponseType.TOKEN); + final String url = AuthUtils.buildUrl(sessionConfiguration.getRedirectUri(), + ResponseType.TOKEN, sessionConfiguration); + + if (AuthUtils.isRedirectUriRegistered(activity, + Uri.parse(sessionConfiguration.getRedirectUri()))) { + loginWithCustomtab(activity, Uri.parse(url)); + } else { + loginWithWebView(activity, ResponseType.TOKEN); + } } /** @@ -189,7 +197,30 @@ public void loginForImplicitGrant(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForAuthorizationCode(@NonNull Activity activity) { - loginWithCustomtab(activity, ResponseType.CODE); + final String url = AuthUtils.buildUrl(sessionConfiguration.getRedirectUri(), + ResponseType.CODE, sessionConfiguration); + + if (AuthUtils.isRedirectUriRegistered(activity, + Uri.parse(sessionConfiguration.getRedirectUri()))) { + loginWithCustomtab(activity, Uri.parse(url)); + } else { + loginWithWebView(activity, ResponseType.CODE); + } + } + + + /** + * Deprecated to use with Ride Request Widget while transitions are being made to registered + * URI redirects + * @param activity + * @param responseType + * @deprecated + */ + @Deprecated + public void loginWithWebView(@NonNull final Activity activity, @NonNull ResponseType + responseType) { + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, responseType); + activity.startActivityForResult(intent, requestCode); } /** @@ -311,23 +342,6 @@ public void onActivityResult( } } - private void loginWithCustomtab(@NonNull final Activity activity, @NonNull ResponseType responseType) { - - final String url = AuthUtils.buildUrl(sessionConfiguration.getRedirectUri(), responseType, - sessionConfiguration); - - if (AuthUtils.isRedirectUriRegistered(activity, - Uri.parse(sessionConfiguration.getRedirectUri()))) { - final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); - - CustomTabsHelper.openCustomTab(activity, intent, Uri.parse(url), - new CustomTabsHelper.BrowserFallback()); - } else { - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, responseType); - activity.startActivityForResult(intent, requestCode); - } - } - /** * Handle the Uber authorization result. * This will parse the Intent to pull the access token or error out of the Data URI, and call @@ -363,6 +377,13 @@ public void handleAuthorizationResult(@NonNull Intent data) { } } + + private void loginWithCustomtab(@NonNull final Activity activity, @NonNull Uri uri) { + final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); + CustomTabsHelper.openCustomTab(activity, intent, uri, new CustomTabsHelper + .BrowserFallback()); + } + private void handleResultCancelled( @NonNull Activity activity, @Nullable Intent data) {// An error occurred during login diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index 08fbd45f..532e61f3 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -131,7 +131,8 @@ public void testButtonClickWithoutLoginManager_shouldCreateNew() { .build(); loginButton = new LoginButton(activity, attributeSet); - loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()); + loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setRedirectUri + ("app://redirecturi").setClientId("clientId").build()); loginButton.setCallback(loginCallback); loginButton.setScopes(SCOPES); loginButton.setAccessTokenStorage(accessTokenStorage); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 5d0e53f4..8a1f61f1 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -100,6 +100,7 @@ public class LoginManagerTest extends RobolectricTestBase { String.format("https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v%s-login_manager", BuildConfig.VERSION_NAME); private static final String AUTHORIZATION_CODE = "Auth123Code"; + private static final String REDIRECT_URI = "app://redirecturi"; @Mock Activity activity; @@ -119,7 +120,8 @@ public class LoginManagerTest extends RobolectricTestBase { @Before public void setup() { - sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID).setScopes(MIXED_SCOPES).build(); + sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID) + .setScopes(MIXED_SCOPES).setRedirectUri(REDIRECT_URI).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); when(activity.getPackageManager()).thenReturn(packageManager); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java index 47f7b107..a04736c4 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java @@ -67,23 +67,6 @@ public void setUp() { client = testLoginActivity.new AccessTokenClient(REDIRECT_URI); } - @Test - public void onBuildUrl_withDefaultRegion_shouldHaveDefaultUberDomain() { - String clientId = "clientId1234"; - String redirectUri = "localHost1234"; - - SessionConfiguration loginConfiguration = new SessionConfiguration.Builder() - .setRedirectUri(redirectUri) - .setScopes(Arrays.asList(Scope.HISTORY)) - .setClientId(clientId) - .build(); - - String url = testLoginActivity.buildUrl(redirectUri, ResponseType.TOKEN, loginConfiguration); - assertEquals("https://login.uber.com/oauth/v2/authorize?client_id=" + clientId + - "&redirect_uri=" + redirectUri + "&response_type=token&scope=history&" + - "show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A", url); - } - @Test public void onLoadLoginView_withNoRedirectUrl_shouldReturnError() { SessionConfiguration config = new SessionConfiguration.Builder().setClientId("clientId").build(); diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index dacfc289..d6a01cb8 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -41,6 +41,7 @@ import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.core.auth.LoginCallback; import com.uber.sdk.android.core.auth.LoginManager; +import com.uber.sdk.android.core.auth.ResponseType; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; @@ -253,7 +254,7 @@ private void load() { } private void login() { - loginManager.login(this); + loginManager.loginWithWebView(this, ResponseType.TOKEN); } /** diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 6a42fc0a..403f20c8 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -154,6 +154,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { blackButton.onActivityResult(requestCode, resultCode, data); + //A temporary measure to account for older Uber app SSO implementations in the wild loginManager.onActivityResult(this, requestCode, resultCode, data); } From 60db706acc97a607d7b99bc5f823604deee9a8de Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 9 Jan 2018 16:59:45 -0800 Subject: [PATCH 032/165] Fixing browsable issue for custom tabs --- README.md | 1 + .../java/com/uber/sdk/android/core/auth/LoginManager.java | 5 ++++- samples/login-sample/src/main/AndroidManifest.xml | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d26176a2..6d984bd5 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ short term until we indicate otherwise. + diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 9c45809c..6e991f6a 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -448,7 +448,10 @@ private void validateRedirectUriRegistration(@NonNull Activity activity) { if (!AuthUtils.isRedirectUriRegistered(activity, Uri.parse(sessionConfiguration .getRedirectUri()))) { - String error = "Must now register redirect_uri in AndroidManifest.xml. See README."; + String error = "Must now register redirect_uri " + sessionConfiguration + .getRedirectUri() + " in an intent filter in the AndroidManifest.xml of the " + + "application. See README in the rides-android-sdk for more information."; + if (Utility.isDebugable(activity)) { throw new IllegalStateException(error); } else { diff --git a/samples/login-sample/src/main/AndroidManifest.xml b/samples/login-sample/src/main/AndroidManifest.xml index ff38adaa..c61f4179 100644 --- a/samples/login-sample/src/main/AndroidManifest.xml +++ b/samples/login-sample/src/main/AndroidManifest.xml @@ -42,6 +42,7 @@ + From e071465cf69f5a027ab10877f2beafed4d4fc6f7 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 9 Jan 2018 18:53:09 -0800 Subject: [PATCH 033/165] Unifying callback for SSO and redirect URIs --- .../android/core/auth/CustomTabsHelper.java | 2 +- .../sdk/android/core/auth/LoginButton.java | 5 + .../sdk/android/core/auth/LoginManager.java | 128 ++++++++---------- .../android/core/auth/LoginManagerTest.java | 28 +--- .../android/rides/RideRequestActivity.java | 2 +- .../rides/RideRequestActivityTest.java | 4 +- .../android/samples/LoginSampleActivity.java | 15 +- 7 files changed, 67 insertions(+), 117 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java index 631a192e..3c6ed7c1 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java @@ -68,7 +68,7 @@ public static void openCustomTab(Activity activity, } } else { customTabsIntent.intent.setPackage(packageName); - customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); customTabsIntent.launchUrl(activity, uri); } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java index ee38ad6c..2e5c00f1 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java @@ -227,6 +227,11 @@ public int getRequestCode() { * @param requestCode request code originally supplied to {@link Activity#startActivityForResult(Intent, int)}. * @param resultCode result code from returning {@link Activity}. * @param data data from returning {@link Activity}. + * + * @deprecated use {@link LoginManager#handleAuthorizationResult(Activity, Intent)} going + * forward. Will be removed in future version once all + * startActivityForResult/onActivityResult code for implicit grant and SSO is switched to + * using redirect URIs. */ public void onActivityResult( int requestCode, diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 6e991f6a..e8096f7d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -107,7 +107,7 @@ public class LoginManager { /** * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} + * @param loginCallback callback to be called when {@link LoginManager#handleAuthorizationResult(Activity, Intent)} * is called. */ public LoginManager( @@ -118,7 +118,7 @@ public LoginManager( /** * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. + * @param loginCallback callback to be called when {@link LoginManager#handleAuthorizationResult(Activity, Intent)} is called. * @param configuration to provide authentication information */ public LoginManager( @@ -130,7 +130,7 @@ public LoginManager( /** * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. + * @param loginCallback callback to be called when {@link LoginManager#handleAuthorizationResult(Activity, Intent)} is called. * @param configuration to provide authentication information * @param requestCode custom code to use for Activity communication */ @@ -325,31 +325,33 @@ private void redirectToInstallApp(@NonNull Activity activity) { * @param requestCode request code originally supplied to {@link Activity#startActivityForResult(Intent, int)}. * @param resultCode result code from returning {@link Activity}. * @param data data from returning {@link Activity}. + * + * @deprecated use {@link LoginManager#handleAuthorizationResult(Activity, Intent)} going + * forward. Will be removed in future version once all + * startActivityForResult/onActivityResult code for implicit grant and SSO is switched to + * using redirect URIs. */ + @Deprecated public void onActivityResult( @NonNull Activity activity, int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode != this.requestCode) { - return; - } - - if (resultCode == Activity.RESULT_OK) { - handleResultOk(data); - } else if (resultCode == Activity.RESULT_CANCELED) { - handleResultCancelled(activity, data); - } + handleAuthorizationResult(activity, data); } /** * Handle the Uber authorization result. * This will parse the Intent to pull the access token or error out of the Data URI, and call - * the set callback. + * the set callback. This will no-op when no Uber Tokens are present. * * @param data */ - public void handleAuthorizationResult(@NonNull Intent data) { + public void handleAuthorizationResult(@NonNull Activity activity, @Nullable Intent data) { + if(data == null) { + return; + } + if (data.getData() != null && data.getData().toString().startsWith(sessionConfiguration.getRedirectUri())) { @@ -370,10 +372,48 @@ public void handleAuthorizationResult(@NonNull Intent data) { try { AccessToken token = AuthUtils.parseTokenUri(fragmentUri); + accessTokenStorage.setAccessToken(token); callback.onLoginSuccess(token); } catch (LoginAuthenticationException e) { callback.onLoginError(e.getAuthenticationError()); } + } else if(data.getStringExtra(EXTRA_ERROR) != null) { + final String error = data.getStringExtra(EXTRA_ERROR); + final AuthenticationError authenticationError + = (error != null) ? AuthenticationError.fromString(error) : AuthenticationError.UNKNOWN; + + if (authenticationError.equals(AuthenticationError.CANCELLED)) { + // User canceled login + callback.onLoginCancel(); + return; + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && + !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { + loginForImplicitGrant(activity); + return; + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) + && redirectForAuthorizationCode) { + loginForAuthorizationCode(activity); + } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { + AppProtocol appProtocol = new AppProtocol(); + String appSignature = appProtocol.getAppSignature(activity); + if (appSignature == null) { + Log.e(UberSdk.UBER_SDK_LOG_TAG, "There was an error obtaining your Application Signature. Please check " + + "your Application Signature and add it to the developer dashboard at https://developer.uber" + + ".com/dashboard"); + } else { + Log.e(UberSdk.UBER_SDK_LOG_TAG, "Your Application Signature, " + appSignature + + ", does not match one of the registered Application Signatures on the developer dashboard. " + + "Check your settings at https://developer.uber.com/dashboard"); + } + } + callback.onLoginError(authenticationError); + } else if(data.getStringExtra(EXTRA_CODE_RECEIVED) != null) { + final String authorizationCode = data.getStringExtra(EXTRA_CODE_RECEIVED); + callback.onAuthorizationCodeReceived(authorizationCode); + } else if (data.getStringExtra(LoginManager.EXTRA_ACCESS_TOKEN) != null) { + AccessToken accessToken = AuthUtils.createAccessToken(data); + accessTokenStorage.setAccessToken(accessToken); + callback.onLoginSuccess(accessToken); } } @@ -384,66 +424,6 @@ private void loginWithCustomtab(@NonNull final Activity activity, @NonNull Uri u .BrowserFallback()); } - private void handleResultCancelled( - @NonNull Activity activity, - @Nullable Intent data) {// An error occurred during login - - if (data == null) { - // User canceled login - callback.onLoginCancel(); - return; - } - - final String error = data.getStringExtra(EXTRA_ERROR); - final AuthenticationError authenticationError - = (error != null) ? AuthenticationError.fromString(error) : AuthenticationError.UNKNOWN; - - if (authenticationError.equals(AuthenticationError.CANCELLED)) { - // User canceled login - callback.onLoginCancel(); - return; - } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && - !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { - loginForImplicitGrant(activity); - return; - } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) - && redirectForAuthorizationCode) { - loginForAuthorizationCode(activity); - } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { - AppProtocol appProtocol = new AppProtocol(); - String appSignature = appProtocol.getAppSignature(activity); - if (appSignature == null) { - Log.e(UberSdk.UBER_SDK_LOG_TAG, "There was an error obtaining your Application Signature. Please check " - + "your Application Signature and add it to the developer dashboard at https://developer.uber" - + ".com/dashboard"); - } else { - Log.e(UberSdk.UBER_SDK_LOG_TAG, "Your Application Signature, " + appSignature - + ", does not match one of the registered Application Signatures on the developer dashboard. " - + "Check your settings at https://developer.uber.com/dashboard"); - } - } - callback.onLoginError(authenticationError); - } - - private void handleResultOk(@Nullable Intent data) { - if (data == null) { - // Unknown error, should never occur - callback.onLoginError(AuthenticationError.UNKNOWN); - return; - } - - final String authorizationCode = data.getStringExtra(EXTRA_CODE_RECEIVED); - if (authorizationCode != null) { - callback.onAuthorizationCodeReceived(authorizationCode); - } else { - AccessToken accessToken = AuthUtils.createAccessToken(data); - accessTokenStorage.setAccessToken(accessToken); - - callback.onLoginSuccess(accessToken); - - } - } - private void validateRedirectUriRegistration(@NonNull Activity activity) { if (!AuthUtils.isRedirectUriRegistered(activity, Uri.parse(sessionConfiguration .getRedirectUri()))) { diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 8a1f61f1..d4664cd8 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -234,12 +234,6 @@ public void onActivityResult_whenResultOkAndHasCode_shouldCallbackSuccess() { assertThat(capturedCode.getValue()).isEqualTo(AUTHORIZATION_CODE); } - @Test - public void onActivityResult_whenResultCanceledAndNoData_shouldCallbackCancel() { - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); - verify(callback).onLoginCancel(); - } - @Test public void onActivityResult_whenResultCanceledAndHasData_shouldCallbackError() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.INVALID_RESPONSE @@ -249,31 +243,11 @@ public void onActivityResult_whenResultCanceledAndHasData_shouldCallbackError() verify(callback).onLoginError(AuthenticationError.INVALID_RESPONSE); } - @Test - public void onActivityResult_whenResultCanceledAndNoData_shouldCancel() { - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); - verify(callback).onLoginCancel(); - } - - @Test - public void onActivityResult_whenResultOkAndNoData_shouldCallbackErrorUnknown() { - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, null); - verify(callback).onLoginError(AuthenticationError.UNKNOWN); - } - - @Test - public void onActivityResult_whenRequestCodeDoesNotMatch_nothingShouldHappen() { - Intent intent = mock(Intent.class); - loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); - verifyZeroInteractions(intent); - verifyZeroInteractions(callback); - } - @Test public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShouldHappen() { Intent intent = mock(Intent.class); loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); - verifyZeroInteractions(intent); + verifyZeroInteractions(callback); } @Test diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index d6a01cb8..c49c282e 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -156,7 +156,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == LOGIN_REQUEST_CODE) { - loginManager.onActivityResult(this, requestCode, resultCode, data); + loginManager.handleAuthorizationResult(this, data); } } diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index 19d7a58d..0a808452 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -30,6 +30,7 @@ import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.core.auth.LoginManager; +import com.uber.sdk.android.core.auth.ResponseType; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; @@ -47,6 +48,7 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; import static org.mockito.Matchers.refEq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -199,7 +201,7 @@ public void onLoad_whenRideRequestViewAuthorizationErrorOccurs_shouldAttemptLogi assertNull(shadowActivity.getResultIntent()); verify(activity.accessTokenStorage, times(1)).removeAccessToken(); - verify(activity.loginManager).login(refEq(activity)); + verify(activity.loginManager).loginWithWebView(refEq(activity), eq(ResponseType.TOKEN)); } @Test diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 403f20c8..1508dbec 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -90,7 +90,6 @@ public class LoginSampleActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); - configuration = new SessionConfiguration.Builder() .setClientId(CLIENT_ID) .setRedirectUri(REDIRECT_URI) @@ -120,6 +119,7 @@ protected void onCreate(Bundle savedInstanceState) { new SampleLoginCallback(), configuration, CUSTOM_BUTTON_REQUEST_CODE); + loginManager.handleAuthorizationResult(this, getIntent()); customButton = (Button) findViewById(R.id.custom_uber_button); customButton.setOnClickListener(new View.OnClickListener() { @@ -138,24 +138,13 @@ protected void onResume() { } } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - loginManager.handleAuthorizationResult(intent); - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(LOG_TAG, String.format("onActivityResult requestCode:[%s] resultCode [%s]", requestCode, resultCode)); - //Allow each a chance to catch it. - whiteButton.onActivityResult(requestCode, resultCode, data); - - blackButton.onActivityResult(requestCode, resultCode, data); - //A temporary measure to account for older Uber app SSO implementations in the wild - loginManager.onActivityResult(this, requestCode, resultCode, data); + loginManager.handleAuthorizationResult(this, data); } private class SampleLoginCallback implements LoginCallback { From e2be3aca793405d2e774b959b47ad22081e0f419 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 11 Jan 2018 14:05:10 -0800 Subject: [PATCH 034/165] Redirect URI param added for SSO --- .../sdk/android/core/auth/LoginManager.java | 1 + .../sdk/android/core/auth/SsoDeeplink.java | 18 ++++++++++++++++-- .../sdk/android/core/utils/AppProtocol.java | 3 ++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index e8096f7d..e8a43bdc 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -161,6 +161,7 @@ public void login(@NonNull Activity activity) { .scopes(sessionConfiguration.getScopes()) .customScopes(sessionConfiguration.getCustomScopes()) .activityRequestCode(requestCode) + .redirectUri(sessionConfiguration.getRedirectUri()) .build(); if (ssoDeeplink.isSupported()) { diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index a1cc8975..7d8280fd 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -61,12 +61,14 @@ public class SsoDeeplink implements Deeplink { private static final String URI_QUERY_PLATFORM = "sdk"; private static final String URI_QUERY_SDK_VERSION = "sdk_version"; private static final String URI_HOST = "connect"; + private static final String URI_QUERY_REDIRECT_URI = "redirect_uri"; private final Activity activity; private final String clientId; private final Collection requestedScopes; private final Collection requestedCustomScopes; private final int requestCode; + private final String redirectUri; AppProtocol appProtocol; @@ -75,12 +77,14 @@ public class SsoDeeplink implements Deeplink { @NonNull String clientId, @NonNull Collection requestedScopes, @NonNull Collection requestedCustomScopes, - int requestCode) { + int requestCode, + @NonNull String redirectUri) { this.activity = activity; this.clientId = clientId; this.requestCode = requestCode; this.requestedScopes = requestedScopes; this.requestedCustomScopes = requestedCustomScopes; + this.redirectUri = redirectUri; appProtocol = new AppProtocol(); } @@ -121,6 +125,7 @@ private Uri createSsoUri() { .appendQueryParameter(URI_QUERY_SCOPE, scopes) .appendQueryParameter(URI_QUERY_PLATFORM, AppProtocol.PLATFORM) .appendQueryParameter(URI_QUERY_SDK_VERSION, BuildConfig.VERSION_NAME) + .appendQueryParameter(URI_QUERY_REDIRECT_URI, redirectUri) .build(); } @@ -153,6 +158,7 @@ public static class Builder { private Collection requestedScopes; private Collection requestedCustomScopes; private int requestCode = DEFAULT_REQUEST_CODE; + private String redirectUri; public Builder(@NonNull Activity activity) { this.activity = activity; @@ -183,8 +189,15 @@ public Builder activityRequestCode(int requestCode) { return this; } + + public Builder redirectUri(@NonNull String redirectUri) { + this.redirectUri = redirectUri; + return this; + } + public SsoDeeplink build() { checkNotNull(clientId, "Client Id must be set"); + checkNotNull(redirectUri, "redirectUri must be set"); checkNotEmpty(requestedScopes, "Scopes must be set."); @@ -195,7 +208,8 @@ public SsoDeeplink build() { if (requestCode == DEFAULT_REQUEST_CODE) { Log.i(UBER_SDK_LOG_TAG, "Request code is not set, using default request code"); } - return new SsoDeeplink(activity, clientId, requestedScopes, requestedCustomScopes, requestCode); + return new SsoDeeplink(activity, clientId, requestedScopes, requestedCustomScopes, + requestCode, redirectUri); } } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 61f8c1e4..8e631a2c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -18,7 +18,8 @@ public class AppProtocol { public static final String[] UBER_PACKAGE_NAMES = - {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo"}; + {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo", + "com.ubercab.presidio.development"}; public static final String DEEPLINK_SCHEME = "uber"; public static final String PLATFORM = "android"; From a1d78fb971bbd03e0046bd8e460efaa18a02f032 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 11 Jan 2018 16:19:13 -0800 Subject: [PATCH 035/165] Creating LoginMangaer as proxy --- core-android/src/main/AndroidManifest.xml | 15 ++- .../android/core/auth/CustomTabsHelper.java | 41 +++++- .../sdk/android/core/auth/LoginActivity.java | 125 ++++++++++++++---- .../auth/LoginRedirectReceiverActivity.java | 20 +++ rides-android/src/main/AndroidManifest.xml | 3 - .../login-sample/src/main/AndroidManifest.xml | 8 -- .../android/samples/LoginSampleActivity.java | 2 + 7 files changed, 168 insertions(+), 46 deletions(-) create mode 100644 core-android/src/main/java/com/uber/sdk/android/core/auth/LoginRedirectReceiverActivity.java diff --git a/core-android/src/main/AndroidManifest.xml b/core-android/src/main/AndroidManifest.xml index 0784cba1..e4cb8e11 100644 --- a/core-android/src/main/AndroidManifest.xml +++ b/core-android/src/main/AndroidManifest.xml @@ -29,6 +29,19 @@ + android:screenOrientation="portrait" + android:launchMode="singleTask"> + + + + + + + + + + diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java index 3c6ed7c1..85d11254 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java @@ -15,6 +15,7 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -23,7 +24,9 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.customtabs.CustomTabsClient; import android.support.customtabs.CustomTabsIntent; +import android.support.customtabs.CustomTabsServiceConnection; import android.text.TextUtils; import android.util.Log; @@ -56,21 +59,45 @@ private CustomTabsHelper() {} * @param uri the Uri to be opened. * @param fallback a CustomTabFallback to be used if Custom Tabs is not available. */ - public static void openCustomTab(Activity activity, - CustomTabsIntent customTabsIntent, - Uri uri, + public static void openCustomTab( + final Activity activity, + final CustomTabsIntent customTabsIntent, + final Uri uri, CustomTabFallback fallback) { - String packageName = getPackageNameToUse(activity); + final String packageName = getPackageNameToUse(activity); if (packageName == null) { if (fallback != null) { fallback.openUri(activity, uri); } } else { - customTabsIntent.intent.setPackage(packageName); - customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - customTabsIntent.launchUrl(activity, uri); + final CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { + @Override + public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) { + client.warmup(0L); // This prevents backgrounding after redirection + + customTabsIntent.intent.setPackage(packageName); + customTabsIntent.intent.setData(uri); + customTabsIntent.launchUrl(activity, uri); + } + @Override + public void onServiceDisconnected(ComponentName name) {} + }; + CustomTabsClient.bindCustomTabsService(activity, packageName, connection); } +// customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | +// Intent.FLAG_ACTIVITY_CLEAR_TASK); +// customTabsIntent.intent.setPackage(packageName); +// customTabsIntent.intent.setData(uri); +// activity.startActivityForResult(customTabsIntent.intent, 20); +// customTabsIntent.launchUrl(activity, uri); + +// } + } + + + static void launchTab(final Context context, final Uri uri){ + } /** diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 2d86caa0..021182f3 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -29,6 +29,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.support.customtabs.CustomTabsIntent; import android.text.TextUtils; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; @@ -46,10 +47,14 @@ public class LoginActivity extends Activity { static final String EXTRA_RESPONSE_TYPE = "RESPONSE_TYPE"; static final String EXTRA_SESSION_CONFIGURATION = "SESSION_CONFIGURATION"; + static final String EXTRA_FORCE_WEBVIEW = "FORCE_WEBVIEW"; + + static final String ERROR = "error"; private WebView webView; private ResponseType responseType; private SessionConfiguration sessionConfiguration; + private LoginManager loginManager; /** * Create an {@link Intent} to pass to this activity @@ -65,20 +70,69 @@ public static Intent newIntent( @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType) { + return newIntent(context, sessionConfiguration, responseType, false); + } + + /** + * Create an {@link Intent} to pass to this activity + * + * @param context the {@link Context} for the intent + * @param sessionConfiguration to be used for gather clientId + * @param responseType that is expected + * @param forceWebview Forced to use old webview instead of chrometabs + * @return an intent that can be passed to this activity + */ + @NonNull + public static Intent newIntent( + @NonNull Context context, + @NonNull SessionConfiguration sessionConfiguration, + @NonNull ResponseType responseType, + boolean forceWebview) { + final Intent data = new Intent(context, LoginActivity.class) .putExtra(EXTRA_SESSION_CONFIGURATION, sessionConfiguration) - .putExtra(EXTRA_RESPONSE_TYPE, responseType); + .putExtra(EXTRA_RESPONSE_TYPE, responseType) + .putExtra(EXTRA_FORCE_WEBVIEW, forceWebview); return data; } + /** + * Used to handle Redirect URI response from customtab or browser + * + * @param context + * @param responseUri + * @return + */ + public static Intent newResponseIntent(Context context, Uri responseUri) { + Intent intent = new Intent(context, LoginActivity.class); + intent.setData(responseUri); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + return intent; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.ub__login_activity); + init(); + } - webView = (WebView) findViewById(R.id.ub__login_webview); + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + init(); + } + + protected void init() { + if(getIntent().getData() != null) { + handleResponse(getIntent().getData()); + } else { + loadUrl(); + } + } + protected void loadUrl() { sessionConfiguration = (SessionConfiguration) getIntent().getSerializableExtra(EXTRA_SESSION_CONFIGURATION); if (sessionConfiguration == null) { onError(AuthenticationError.UNAVAILABLE); @@ -102,11 +156,50 @@ protected void onCreate(Bundle savedInstanceState) { return; } + String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration); + + if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)) { + loadWebview(url, redirectUri); + } else { + loadChrometab(url); + } + } + + protected boolean handleResponse(@NonNull Uri uri) { + final String fragment = uri.getFragment(); + + if (fragment == null) { + onError(AuthenticationError.INVALID_RESPONSE); + return true; + } + + final Uri fragmentUri = new Uri.Builder().encodedQuery(fragment).build(); + + // In case fragment contains error, we want to handle that too. + final String error = fragmentUri.getQueryParameter(ERROR); + if (!TextUtils.isEmpty(error)) { + onError(AuthenticationError.fromString(error)); + return true; + } + + onTokenReceived(fragmentUri); + return true; + } + + protected void loadWebview(String url, String redirectUri) { + setContentView(R.layout.ub__login_activity); + webView = (WebView) findViewById(R.id.ub__login_webview); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setAppCacheEnabled(true); webView.getSettings().setDomStorageEnabled(true); webView.setWebViewClient(createOAuthClient(redirectUri)); - webView.loadUrl(AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration)); + webView.loadUrl(url); + } + + protected void loadChrometab(String url) { + final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); + CustomTabsHelper.openCustomTab(this, intent, Uri.parse(url), new CustomTabsHelper + .BrowserFallback()); } protected OAuthWebViewClient createOAuthClient(String redirectUri) { @@ -154,8 +247,6 @@ void onCodeReceived(Uri uri) { @VisibleForTesting abstract class OAuthWebViewClient extends WebViewClient { - protected static final String ERROR = "error"; - @NonNull protected final String redirectUri; @@ -213,27 +304,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { } if (url.startsWith(redirectUri)) { - - //OAuth 2 spec requires the access token in URL Fragment instead of query parameters. - //Swap Fragment with Query to facilitate parsing. - final String fragment = uri.getFragment(); - - if (fragment == null) { - onError(AuthenticationError.INVALID_RESPONSE); - return true; - } - - final Uri fragmentUri = new Uri.Builder().encodedQuery(fragment).build(); - - // In case fragment contains error, we want to handle that too. - final String error = fragmentUri.getQueryParameter(ERROR); - if (!TextUtils.isEmpty(error)) { - onError(AuthenticationError.fromString(error)); - return true; - } - - onTokenReceived(fragmentUri); - return true; + return handleResponse(uri); } return super.shouldOverrideUrlLoading(view, url); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginRedirectReceiverActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginRedirectReceiverActivity.java new file mode 100644 index 00000000..90060876 --- /dev/null +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginRedirectReceiverActivity.java @@ -0,0 +1,20 @@ +package com.uber.sdk.android.core.auth; + +import android.app.Activity; +import android.os.Bundle; + +public class LoginRedirectReceiverActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceBundle) { + super.onCreate(savedInstanceBundle); + + // while this does not appear to be achieving much, handling the redirect in this way + // ensures that we can remove the browser tab from the back stack. See the documentation + // on AuthorizationManagementActivity for more details. + startActivity(LoginActivity.newResponseIntent( + this, getIntent().getData())); + finish(); + } + +} diff --git a/rides-android/src/main/AndroidManifest.xml b/rides-android/src/main/AndroidManifest.xml index aafec2fb..33c1f5fa 100644 --- a/rides-android/src/main/AndroidManifest.xml +++ b/rides-android/src/main/AndroidManifest.xml @@ -30,8 +30,5 @@ - diff --git a/samples/login-sample/src/main/AndroidManifest.xml b/samples/login-sample/src/main/AndroidManifest.xml index c61f4179..3bb26e75 100644 --- a/samples/login-sample/src/main/AndroidManifest.xml +++ b/samples/login-sample/src/main/AndroidManifest.xml @@ -38,14 +38,6 @@ - - - - - - - diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 1508dbec..927b67a9 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -41,6 +41,7 @@ import com.uber.sdk.android.core.auth.LoginButton; import com.uber.sdk.android.core.auth.LoginCallback; import com.uber.sdk.android.core.auth.LoginManager; +import com.uber.sdk.android.core.auth.ResponseType; import com.uber.sdk.android.rides.samples.BuildConfig; import com.uber.sdk.android.rides.samples.R; import com.uber.sdk.core.auth.AccessToken; @@ -125,6 +126,7 @@ protected void onCreate(Bundle savedInstanceState) { customButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + loginManager.login(LoginSampleActivity.this); } }); From e7b99e5c6a33e25793d63fd134cfbefcacfdbfea Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 11 Jan 2018 17:26:56 -0800 Subject: [PATCH 036/165] Removing uneeded changes --- .../uber/sdk/android/core/auth/AuthUtils.java | 20 +- .../sdk/android/core/auth/LoginActivity.java | 24 ++- .../sdk/android/core/auth/LoginButton.java | 5 - .../sdk/android/core/auth/LoginManager.java | 198 +++++++----------- .../sdk/android/core/auth/SsoDeeplink.java | 22 +- .../sdk/android/core/auth/AuthUtilsTest.java | 19 ++ .../android/core/auth/LoginButtonTest.java | 3 +- .../android/core/auth/LoginManagerTest.java | 32 ++- .../android/rides/RideRequestActivity.java | 11 +- .../rides/RideRequestActivityTest.java | 4 +- .../android/samples/LoginSampleActivity.java | 50 ++--- 11 files changed, 183 insertions(+), 205 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index b7e539b1..cc8a6b1e 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -23,7 +23,9 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.net.Uri; import android.support.annotation.NonNull; @@ -129,17 +131,15 @@ static Collection stringCollectionToScopeCollection(@NonNull Collection resolvedActivityList = activity.getPackageManager() - .queryIntentActivities(activityIntent, 0); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(uri); + ComponentName info = intent.resolveActivity(activity.getPackageManager()); + + return info != null && info.getClassName().equals(LoginRedirectReceiverActivity.class + .getName()); + - boolean supported = false; - for (ResolveInfo resolveInfo : resolvedActivityList) { - if (resolveInfo.activityInfo.packageName.equals(activity.getPackageName())) { - supported = true; - } - } - return supported; } /** diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 021182f3..b634bc83 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -55,6 +55,7 @@ public class LoginActivity extends Activity { private ResponseType responseType; private SessionConfiguration sessionConfiguration; private LoginManager loginManager; + private boolean authStarted; /** * Create an {@link Intent} to pass to this activity @@ -117,9 +118,24 @@ protected void onCreate(Bundle savedInstanceState) { init(); } + @Override + protected void onResume() { + super.onResume(); + + if(webView == null) { + if(!authStarted) { + authStarted = true; + return; + } + + finish(); + } + } + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + authStarted = false; setIntent(intent); init(); } @@ -150,14 +166,10 @@ protected void loadUrl() { onError(AuthenticationError.UNAVAILABLE); } - final String redirectUri = sessionConfiguration.getRedirectUri(); - if (redirectUri == null) { - onError(AuthenticationError.INVALID_REDIRECT_URI); - return; - } + String redirectUri = sessionConfiguration.getRedirectUri() != null ? sessionConfiguration + .getRedirectUri() : getApplicationContext().getPackageName() + "uberauth"; String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration); - if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)) { loadWebview(url, redirectUri); } else { diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java index 2e5c00f1..ee38ad6c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java @@ -227,11 +227,6 @@ public int getRequestCode() { * @param requestCode request code originally supplied to {@link Activity#startActivityForResult(Intent, int)}. * @param resultCode result code from returning {@link Activity}. * @param data data from returning {@link Activity}. - * - * @deprecated use {@link LoginManager#handleAuthorizationResult(Activity, Intent)} going - * forward. Will be removed in future version once all - * startActivityForResult/onActivityResult code for implicit grant and SSO is switched to - * using redirect URIs. */ public void onActivityResult( int requestCode, diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index e8a43bdc..93110a9d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -27,8 +27,6 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.customtabs.CustomTabsIntent; -import android.text.TextUtils; import android.util.Log; import com.uber.sdk.android.core.BuildConfig; @@ -58,11 +56,6 @@ public class LoginManager { */ static final String EXTRA_ERROR = "ERROR"; - /** - * Used to retrieve the {@link AuthenticationError} from a result URI - */ - static final String QUERY_PARAM_ERROR = "error"; - /** * Used to retrieve the Access Token from an {@link Intent}. */ @@ -107,7 +100,7 @@ public class LoginManager { /** * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#handleAuthorizationResult(Activity, Intent)} + * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} * is called. */ public LoginManager( @@ -118,7 +111,7 @@ public LoginManager( /** * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#handleAuthorizationResult(Activity, Intent)} is called. + * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. * @param configuration to provide authentication information */ public LoginManager( @@ -130,7 +123,7 @@ public LoginManager( /** * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#handleAuthorizationResult(Activity, Intent)} is called. + * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. * @param configuration to provide authentication information * @param requestCode custom code to use for Activity communication */ @@ -154,14 +147,11 @@ public void login(@NonNull Activity activity) { checkNotEmpty(sessionConfiguration.getScopes(), "Scopes must be set in the Session " + "Configuration."); - validateRedirectUriRegistration(activity); - SsoDeeplink ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(sessionConfiguration.getClientId()) .scopes(sessionConfiguration.getScopes()) .customScopes(sessionConfiguration.getCustomScopes()) .activityRequestCode(requestCode) - .redirectUri(sessionConfiguration.getRedirectUri()) .build(); if (ssoDeeplink.isSupported()) { @@ -181,15 +171,9 @@ public void login(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForImplicitGrant(@NonNull Activity activity) { - final String url = AuthUtils.buildUrl(sessionConfiguration.getRedirectUri(), - ResponseType.TOKEN, sessionConfiguration); - - if (AuthUtils.isRedirectUriRegistered(activity, - Uri.parse(sessionConfiguration.getRedirectUri()))) { - loginWithCustomtab(activity, Uri.parse(url)); - } else { - loginWithWebView(activity, ResponseType.TOKEN); - } + validateRedirectUriRegistration(activity); + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN); + activity.startActivityForResult(intent, requestCode); } /** @@ -198,29 +182,8 @@ public void loginForImplicitGrant(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForAuthorizationCode(@NonNull Activity activity) { - final String url = AuthUtils.buildUrl(sessionConfiguration.getRedirectUri(), - ResponseType.CODE, sessionConfiguration); - - if (AuthUtils.isRedirectUriRegistered(activity, - Uri.parse(sessionConfiguration.getRedirectUri()))) { - loginWithCustomtab(activity, Uri.parse(url)); - } else { - loginWithWebView(activity, ResponseType.CODE); - } - } - - - /** - * Deprecated to use with Ride Request Widget while transitions are being made to registered - * URI redirects - * @param activity - * @param responseType - * @deprecated - */ - @Deprecated - public void loginWithWebView(@NonNull final Activity activity, @NonNull ResponseType - responseType) { - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, responseType); + validateRedirectUriRegistration(activity); + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.CODE); activity.startActivityForResult(intent, requestCode); } @@ -326,112 +289,93 @@ private void redirectToInstallApp(@NonNull Activity activity) { * @param requestCode request code originally supplied to {@link Activity#startActivityForResult(Intent, int)}. * @param resultCode result code from returning {@link Activity}. * @param data data from returning {@link Activity}. - * - * @deprecated use {@link LoginManager#handleAuthorizationResult(Activity, Intent)} going - * forward. Will be removed in future version once all - * startActivityForResult/onActivityResult code for implicit grant and SSO is switched to - * using redirect URIs. */ - @Deprecated public void onActivityResult( @NonNull Activity activity, int requestCode, int resultCode, @Nullable Intent data) { - handleAuthorizationResult(activity, data); - } - - /** - * Handle the Uber authorization result. - * This will parse the Intent to pull the access token or error out of the Data URI, and call - * the set callback. This will no-op when no Uber Tokens are present. - * - * @param data - */ - public void handleAuthorizationResult(@NonNull Activity activity, @Nullable Intent data) { - if(data == null) { + if (requestCode != this.requestCode) { return; } - if (data.getData() != null - && data.getData().toString().startsWith(sessionConfiguration.getRedirectUri())) { + if (resultCode == Activity.RESULT_OK) { + handleResultOk(data); + } else if (resultCode == Activity.RESULT_CANCELED) { + handleResultCancelled(activity, data); + } + } - final String fragment = data.getData().getFragment(); + private void handleResultCancelled( + @NonNull Activity activity, + @Nullable Intent data) {// An error occurred during login - if (fragment == null) { - callback.onLoginError(AuthenticationError.INVALID_RESPONSE); - return; - } + if (data == null) { + // User canceled login + callback.onLoginCancel(); + return; + } - final Uri fragmentUri = new Uri.Builder().encodedQuery(fragment).build(); + final String error = data.getStringExtra(EXTRA_ERROR); + final AuthenticationError authenticationError + = (error != null) ? AuthenticationError.fromString(error) : AuthenticationError.UNKNOWN; - final String error = fragmentUri.getQueryParameter(QUERY_PARAM_ERROR); - if (!TextUtils.isEmpty(error)) { - callback.onLoginError(AuthenticationError.fromString(error)); - return; + if (authenticationError.equals(AuthenticationError.CANCELLED)) { + // User canceled login + callback.onLoginCancel(); + return; + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && + !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { + loginForImplicitGrant(activity); + return; + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) + && redirectForAuthorizationCode) { + loginForAuthorizationCode(activity); + } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { + AppProtocol appProtocol = new AppProtocol(); + String appSignature = appProtocol.getAppSignature(activity); + if (appSignature == null) { + Log.e(UberSdk.UBER_SDK_LOG_TAG, "There was an error obtaining your Application Signature. Please check " + + "your Application Signature and add it to the developer dashboard at https://developer.uber" + + ".com/dashboard"); + } else { + Log.e(UberSdk.UBER_SDK_LOG_TAG, "Your Application Signature, " + appSignature + + ", does not match one of the registered Application Signatures on the developer dashboard. " + + "Check your settings at https://developer.uber.com/dashboard"); } + } + callback.onLoginError(authenticationError); + } - try { - AccessToken token = AuthUtils.parseTokenUri(fragmentUri); - accessTokenStorage.setAccessToken(token); - callback.onLoginSuccess(token); - } catch (LoginAuthenticationException e) { - callback.onLoginError(e.getAuthenticationError()); - } - } else if(data.getStringExtra(EXTRA_ERROR) != null) { - final String error = data.getStringExtra(EXTRA_ERROR); - final AuthenticationError authenticationError - = (error != null) ? AuthenticationError.fromString(error) : AuthenticationError.UNKNOWN; - - if (authenticationError.equals(AuthenticationError.CANCELLED)) { - // User canceled login - callback.onLoginCancel(); - return; - } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && - !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { - loginForImplicitGrant(activity); - return; - } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) - && redirectForAuthorizationCode) { - loginForAuthorizationCode(activity); - } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { - AppProtocol appProtocol = new AppProtocol(); - String appSignature = appProtocol.getAppSignature(activity); - if (appSignature == null) { - Log.e(UberSdk.UBER_SDK_LOG_TAG, "There was an error obtaining your Application Signature. Please check " - + "your Application Signature and add it to the developer dashboard at https://developer.uber" - + ".com/dashboard"); - } else { - Log.e(UberSdk.UBER_SDK_LOG_TAG, "Your Application Signature, " + appSignature - + ", does not match one of the registered Application Signatures on the developer dashboard. " - + "Check your settings at https://developer.uber.com/dashboard"); - } - } - callback.onLoginError(authenticationError); - } else if(data.getStringExtra(EXTRA_CODE_RECEIVED) != null) { - final String authorizationCode = data.getStringExtra(EXTRA_CODE_RECEIVED); + private void handleResultOk(@Nullable Intent data) { + if (data == null) { + // Unknown error, should never occur + callback.onLoginError(AuthenticationError.UNKNOWN); + return; + } + + final String authorizationCode = data.getStringExtra(EXTRA_CODE_RECEIVED); + if (authorizationCode != null) { callback.onAuthorizationCodeReceived(authorizationCode); - } else if (data.getStringExtra(LoginManager.EXTRA_ACCESS_TOKEN) != null) { + } else { AccessToken accessToken = AuthUtils.createAccessToken(data); accessTokenStorage.setAccessToken(accessToken); - callback.onLoginSuccess(accessToken); - } - } + callback.onLoginSuccess(accessToken); - private void loginWithCustomtab(@NonNull final Activity activity, @NonNull Uri uri) { - final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); - CustomTabsHelper.openCustomTab(activity, intent, uri, new CustomTabsHelper - .BrowserFallback()); + } } private void validateRedirectUriRegistration(@NonNull Activity activity) { - if (!AuthUtils.isRedirectUriRegistered(activity, Uri.parse(sessionConfiguration - .getRedirectUri()))) { - String error = "Must now register redirect_uri " + sessionConfiguration - .getRedirectUri() + " in an intent filter in the AndroidManifest.xml of the " - + "application. See README in the rides-android-sdk for more information."; + String generatedRedirectUri = activity.getPackageName().concat("uberauth"); + String setRedirectUri = sessionConfiguration.getRedirectUri(); + if (setRedirectUri != null && + !generatedRedirectUri.equals(setRedirectUri) + && !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { + String error = "Misconfigured redirect_uri in " + sessionConfiguration + .getRedirectUri() + " and the LoginRedirectReceiverActivity to receive the " + + "response. See https://github.com/uber/rides-android-sdk for more info."; if (Utility.isDebugable(activity)) { throw new IllegalStateException(error); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 7d8280fd..25b3f204 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -61,14 +61,12 @@ public class SsoDeeplink implements Deeplink { private static final String URI_QUERY_PLATFORM = "sdk"; private static final String URI_QUERY_SDK_VERSION = "sdk_version"; private static final String URI_HOST = "connect"; - private static final String URI_QUERY_REDIRECT_URI = "redirect_uri"; private final Activity activity; private final String clientId; private final Collection requestedScopes; private final Collection requestedCustomScopes; private final int requestCode; - private final String redirectUri; AppProtocol appProtocol; @@ -77,14 +75,12 @@ public class SsoDeeplink implements Deeplink { @NonNull String clientId, @NonNull Collection requestedScopes, @NonNull Collection requestedCustomScopes, - int requestCode, - @NonNull String redirectUri) { + int requestCode) { this.activity = activity; this.clientId = clientId; this.requestCode = requestCode; this.requestedScopes = requestedScopes; this.requestedCustomScopes = requestedCustomScopes; - this.redirectUri = redirectUri; appProtocol = new AppProtocol(); } @@ -116,8 +112,8 @@ public void execute() { private Uri createSsoUri() { String scopes = AuthUtils.scopeCollectionToString(requestedScopes); if (!requestedCustomScopes.isEmpty()) { - scopes = AuthUtils.mergeScopeStrings(scopes, - AuthUtils.customScopeCollectionToString(requestedCustomScopes)); + scopes = AuthUtils.mergeScopeStrings(scopes, + AuthUtils.customScopeCollectionToString(requestedCustomScopes)); } return new Uri.Builder().scheme(AppProtocol.DEEPLINK_SCHEME) .authority(URI_HOST) @@ -125,7 +121,6 @@ private Uri createSsoUri() { .appendQueryParameter(URI_QUERY_SCOPE, scopes) .appendQueryParameter(URI_QUERY_PLATFORM, AppProtocol.PLATFORM) .appendQueryParameter(URI_QUERY_SDK_VERSION, BuildConfig.VERSION_NAME) - .appendQueryParameter(URI_QUERY_REDIRECT_URI, redirectUri) .build(); } @@ -158,7 +153,6 @@ public static class Builder { private Collection requestedScopes; private Collection requestedCustomScopes; private int requestCode = DEFAULT_REQUEST_CODE; - private String redirectUri; public Builder(@NonNull Activity activity) { this.activity = activity; @@ -189,15 +183,8 @@ public Builder activityRequestCode(int requestCode) { return this; } - - public Builder redirectUri(@NonNull String redirectUri) { - this.redirectUri = redirectUri; - return this; - } - public SsoDeeplink build() { checkNotNull(clientId, "Client Id must be set"); - checkNotNull(redirectUri, "redirectUri must be set"); checkNotEmpty(requestedScopes, "Scopes must be set."); @@ -208,8 +195,7 @@ public SsoDeeplink build() { if (requestCode == DEFAULT_REQUEST_CODE) { Log.i(UBER_SDK_LOG_TAG, "Request code is not set, using default request code"); } - return new SsoDeeplink(activity, clientId, requestedScopes, requestedCustomScopes, - requestCode, redirectUri); + return new SsoDeeplink(activity, clientId, requestedScopes, requestedCustomScopes, requestCode); } } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index 69d0bc17..550e3468 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -27,10 +27,12 @@ import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; +import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static junit.framework.Assert.assertEquals; @@ -260,4 +262,21 @@ public void getCodeFromUrl_whenNoValidAuthorizationCodePassed() throws LoginAuth public void testCreateEncodedParam() { assertThat(AuthUtils.createEncodedParam("{\"redirect_to_login\":true}")).isEqualTo("eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0=\n"); } + + @Test + public void onBuildUrl_withDefaultRegion_shouldHaveDefaultUberDomain() { + String clientId = "clientId1234"; + String redirectUri = "localHost1234"; + + SessionConfiguration loginConfiguration = new SessionConfiguration.Builder() + .setRedirectUri(redirectUri) + .setScopes(Arrays.asList(Scope.HISTORY)) + .setClientId(clientId) + .build(); + + String url = AuthUtils.buildUrl(redirectUri, ResponseType.TOKEN, loginConfiguration); + assertEquals("https://login.uber.com/oauth/v2/authorize?client_id=" + clientId + + "&redirect_uri=" + redirectUri + "&response_type=token&scope=history&" + + "show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A", url); + } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index 532e61f3..08fbd45f 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -131,8 +131,7 @@ public void testButtonClickWithoutLoginManager_shouldCreateNew() { .build(); loginButton = new LoginButton(activity, attributeSet); - loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setRedirectUri - ("app://redirecturi").setClientId("clientId").build()); + loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()); loginButton.setCallback(loginCallback); loginButton.setScopes(SCOPES); loginButton.setAccessTokenStorage(accessTokenStorage); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index d4664cd8..5d0e53f4 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -100,7 +100,6 @@ public class LoginManagerTest extends RobolectricTestBase { String.format("https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v%s-login_manager", BuildConfig.VERSION_NAME); private static final String AUTHORIZATION_CODE = "Auth123Code"; - private static final String REDIRECT_URI = "app://redirecturi"; @Mock Activity activity; @@ -120,8 +119,7 @@ public class LoginManagerTest extends RobolectricTestBase { @Before public void setup() { - sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID) - .setScopes(MIXED_SCOPES).setRedirectUri(REDIRECT_URI).build(); + sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID).setScopes(MIXED_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); when(activity.getPackageManager()).thenReturn(packageManager); @@ -234,6 +232,12 @@ public void onActivityResult_whenResultOkAndHasCode_shouldCallbackSuccess() { assertThat(capturedCode.getValue()).isEqualTo(AUTHORIZATION_CODE); } + @Test + public void onActivityResult_whenResultCanceledAndNoData_shouldCallbackCancel() { + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); + verify(callback).onLoginCancel(); + } + @Test public void onActivityResult_whenResultCanceledAndHasData_shouldCallbackError() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.INVALID_RESPONSE @@ -244,12 +248,32 @@ public void onActivityResult_whenResultCanceledAndHasData_shouldCallbackError() } @Test - public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShouldHappen() { + public void onActivityResult_whenResultCanceledAndNoData_shouldCancel() { + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); + verify(callback).onLoginCancel(); + } + + @Test + public void onActivityResult_whenResultOkAndNoData_shouldCallbackErrorUnknown() { + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, null); + verify(callback).onLoginError(AuthenticationError.UNKNOWN); + } + + @Test + public void onActivityResult_whenRequestCodeDoesNotMatch_nothingShouldHappen() { Intent intent = mock(Intent.class); loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); + verifyZeroInteractions(intent); verifyZeroInteractions(callback); } + @Test + public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShouldHappen() { + Intent intent = mock(Intent.class); + loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); + verifyZeroInteractions(intent); + } + @Test public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAuthorizationCode() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index c49c282e..8964c311 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -41,7 +41,6 @@ import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.core.auth.LoginCallback; import com.uber.sdk.android.core.auth.LoginManager; -import com.uber.sdk.android.core.auth.ResponseType; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; @@ -95,9 +94,9 @@ public class RideRequestActivity extends Activity implements LoginCallback, Ride */ @NonNull public static Intent newIntent(@NonNull Context context, - @Nullable RideParameters rideParameters, - @NonNull SessionConfiguration loginConfiguration, - @Nullable String accessTokenStorageKey) { + @Nullable RideParameters rideParameters, + @NonNull SessionConfiguration loginConfiguration, + @Nullable String accessTokenStorageKey) { Intent data = new Intent(context, RideRequestActivity.class); if (rideParameters == null) { @@ -156,7 +155,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == LOGIN_REQUEST_CODE) { - loginManager.handleAuthorizationResult(this, data); + loginManager.onActivityResult(this, requestCode, resultCode, data); } } @@ -254,7 +253,7 @@ private void load() { } private void login() { - loginManager.loginWithWebView(this, ResponseType.TOKEN); + loginManager.login(this); } /** diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index 0a808452..19d7a58d 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -30,7 +30,6 @@ import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.core.auth.LoginManager; -import com.uber.sdk.android.core.auth.ResponseType; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; @@ -48,7 +47,6 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; import static org.mockito.Matchers.refEq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -201,7 +199,7 @@ public void onLoad_whenRideRequestViewAuthorizationErrorOccurs_shouldAttemptLogi assertNull(shadowActivity.getResultIntent()); verify(activity.accessTokenStorage, times(1)).removeAccessToken(); - verify(activity.loginManager).loginWithWebView(refEq(activity), eq(ResponseType.TOKEN)); + verify(activity.loginManager).login(refEq(activity)); } @Test diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 927b67a9..d66c1144 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -41,7 +41,6 @@ import com.uber.sdk.android.core.auth.LoginButton; import com.uber.sdk.android.core.auth.LoginCallback; import com.uber.sdk.android.core.auth.LoginManager; -import com.uber.sdk.android.core.auth.ResponseType; import com.uber.sdk.android.rides.samples.BuildConfig; import com.uber.sdk.android.rides.samples.R; import com.uber.sdk.core.auth.AccessToken; @@ -91,6 +90,7 @@ public class LoginSampleActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); + configuration = new SessionConfiguration.Builder() .setClientId(CLIENT_ID) .setRedirectUri(REDIRECT_URI) @@ -104,15 +104,15 @@ protected void onCreate(Bundle savedInstanceState) { //Create a button with a custom request code whiteButton = (LoginButton) findViewById(R.id.uber_button_white); whiteButton.setCallback(new SampleLoginCallback()) - .setSessionConfiguration(configuration); + .setSessionConfiguration(configuration); //Create a button using a custom AccessTokenStorage //Custom Scopes are set using XML for this button as well in R.layout.activity_sample blackButton = (LoginButton) findViewById(R.id.uber_button_black); blackButton.setAccessTokenStorage(accessTokenStorage) - .setCallback(new SampleLoginCallback()) - .setSessionConfiguration(configuration) - .setRequestCode(LOGIN_BUTTON_CUSTOM_REQUEST_CODE); + .setCallback(new SampleLoginCallback()) + .setSessionConfiguration(configuration) + .setRequestCode(LOGIN_BUTTON_CUSTOM_REQUEST_CODE); //Use a custom button with an onClickListener to call the LoginManager directly @@ -120,13 +120,11 @@ protected void onCreate(Bundle savedInstanceState) { new SampleLoginCallback(), configuration, CUSTOM_BUTTON_REQUEST_CODE); - loginManager.handleAuthorizationResult(this, getIntent()); customButton = (Button) findViewById(R.id.custom_uber_button); customButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - loginManager.login(LoginSampleActivity.this); } }); @@ -145,8 +143,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(LOG_TAG, String.format("onActivityResult requestCode:[%s] resultCode [%s]", requestCode, resultCode)); - //A temporary measure to account for older Uber app SSO implementations in the wild - loginManager.handleAuthorizationResult(this, data); + //Allow each a chance to catch it. + whiteButton.onActivityResult(requestCode, resultCode, data); + + blackButton.onActivityResult(requestCode, resultCode, data); + + loginManager.onActivityResult(this, requestCode, resultCode, data); } private class SampleLoginCallback implements LoginCallback { @@ -182,21 +184,21 @@ private void loadProfileInfo() { service.getUserProfile() .enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Toast.makeText(LoginSampleActivity.this, getString(R.string.greeting, response.body().getFirstName()), Toast.LENGTH_LONG).show(); - } else { - ApiError error = ErrorParser.parseError(response); - Toast.makeText(LoginSampleActivity.this, error.getClientErrors().get(0).getTitle(), Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - - } - }); + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(LoginSampleActivity.this, getString(R.string.greeting, response.body().getFirstName()), Toast.LENGTH_LONG).show(); + } else { + ApiError error = ErrorParser.parseError(response); + Toast.makeText(LoginSampleActivity.this, error.getClientErrors().get(0).getTitle(), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); } @Override From 68b0c51fdf77dc7d8ccab6a0c3d798d8f1812f1c Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 11 Jan 2018 17:37:43 -0800 Subject: [PATCH 037/165] Updating Readme --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6d984bd5..a5fc2472 100644 --- a/README.md +++ b/README.md @@ -201,35 +201,35 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data){ ``` #### Authentication Migration (Version 0.8 and above) +With Version 0.8 and above of the SDK, the redirect URI is more strongly enforced to meet IETF +standards [IETF RFC](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12). -We are moving the startActivityForResult/onActivityResult contract to use the standard URI -contract indicated in the [IETF RFC](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12). +The SDK will automatically created a redirect URI to be used in the oauth callbacks with +the format "applicationId.uberauth", ex "com.example.uberauth". If this differs from the previous +specified redirect URI configured in the SessionConfiguration, there are two options. -Now, you must additionally specify your redirect_uri in the Android Manifest and -proxy that information to the Uber SDK. The recommended format is com.example.yourpackage://redirect-uri + 1. Change the redirect URI to match the new scheme in the configuration of the Session. If this is left out entirely, the default will be used. -To handle migrations from older Uber apps using the old contract, we recommend you implement both -approaches in the -short term until we indicate otherwise. - -```xml - - - - - - +```java +SessionConfiguration config = new SessionConfiguration.Builder() + .setRedirectUri("com.example.app.uberauth") + .build(); ``` -Following this change, you must proxy the results to the `LoginManager` from the called activity. + 2. Override the LoginRedirectReceiverActivity in your main manifest and provide a custom intent +filter. -```java -@Override -protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - loginManager.handleAuthorizationResult(intent); -} +```xml + + + + + + + + ``` The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if that is unavailable, fallback to Implicit Grant if privileged scopes are not requested, otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager.setRedirectForAuthorizationCode(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). From 0e487a1b243f3e12731663b14794a12c5598d69a Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 11 Jan 2018 17:50:17 -0800 Subject: [PATCH 038/165] Fixing fallbacks --- .../com/uber/sdk/android/core/auth/LoginActivity.java | 5 ++++- .../com/uber/sdk/android/core/auth/LoginManager.java | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index b634bc83..1a7761ac 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -169,8 +169,11 @@ protected void loadUrl() { String redirectUri = sessionConfiguration.getRedirectUri() != null ? sessionConfiguration .getRedirectUri() : getApplicationContext().getPackageName() + "uberauth"; + boolean registeredRedirectUri = AuthUtils.isRedirectUriRegistered(this, + Uri.parse(redirectUri)); + String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration); - if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)) { + if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false) || !registeredRedirectUri) { loadWebview(url, redirectUri); } else { loadChrometab(url); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 93110a9d..8c4f20f2 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -368,14 +368,17 @@ private void handleResultOk(@Nullable Intent data) { private void validateRedirectUriRegistration(@NonNull Activity activity) { - String generatedRedirectUri = activity.getPackageName().concat("uberauth"); + String generatedRedirectUri = activity.getPackageName().concat(".uberauth://redirect"); String setRedirectUri = sessionConfiguration.getRedirectUri(); if (setRedirectUri != null && !generatedRedirectUri.equals(setRedirectUri) && !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { - String error = "Misconfigured redirect_uri in " + sessionConfiguration - .getRedirectUri() + " and the LoginRedirectReceiverActivity to receive the " - + "response. See https://github.com/uber/rides-android-sdk for more info."; + String error = "Misconfigured redirect_uri. See https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above " + + "for more info. Either 1) Register " + generatedRedirectUri + " as a " + + "redirect uri for the app at https://developer.uber.com/dashboard/ and " + + "specify this in your SessionConfiguration or 2) Override the default " + + "redirect_uri with the current one set (" + setRedirectUri + ") in the " + + "AndroidManifest."; if (Utility.isDebugable(activity)) { throw new IllegalStateException(error); From 3cdd4ccf0ce2ecf55307f4d8480b91eb5c9a2c06 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 16 Jan 2018 15:42:36 -0800 Subject: [PATCH 039/165] Resolving broken unit tests --- .../java/com/uber/sdk/android/core/auth/LoginManagerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 5d0e53f4..7b937b27 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -124,6 +124,7 @@ public void setup() { when(activity.getPackageManager()).thenReturn(packageManager); when(activity.getApplicationInfo()).thenReturn(new ApplicationInfo()); + when(activity.getPackageName()).thenReturn("com.example"); } @Test From bc86bf62fb674d90f4af23935754449485d94f00 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Wed, 24 Jan 2018 13:37:10 -0800 Subject: [PATCH 040/165] Addressing feedback --- .../android/core/auth/CustomTabsHelper.java | 26 ++++++------------- .../sdk/android/core/auth/LoginManager.java | 2 +- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java index 85d11254..2b287699 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java @@ -30,6 +30,8 @@ import android.text.TextUtils; import android.util.Log; +import com.uber.sdk.android.core.UberSdk; + import java.util.ArrayList; import java.util.List; @@ -66,11 +68,7 @@ public static void openCustomTab( CustomTabFallback fallback) { final String packageName = getPackageNameToUse(activity); - if (packageName == null) { - if (fallback != null) { - fallback.openUri(activity, uri); - } - } else { + if (packageName != null) { final CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) { @@ -84,20 +82,12 @@ public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabs public void onServiceDisconnected(ComponentName name) {} }; CustomTabsClient.bindCustomTabsService(activity, packageName, connection); + } else if (fallback != null) { + fallback.openUri(activity, uri); + } else { + Log.e(UberSdk.UBER_SDK_LOG_TAG, + "Use of openCustomTab without Customtab support or a fallback set"); } -// customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | -// Intent.FLAG_ACTIVITY_CLEAR_TASK); -// customTabsIntent.intent.setPackage(packageName); -// customTabsIntent.intent.setData(uri); -// activity.startActivityForResult(customTabsIntent.intent, 20); -// customTabsIntent.launchUrl(activity, uri); - -// } - } - - - static void launchTab(final Context context, final Uri uri){ - } /** diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 8c4f20f2..68fd4d97 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -383,7 +383,7 @@ private void validateRedirectUriRegistration(@NonNull Activity activity) { if (Utility.isDebugable(activity)) { throw new IllegalStateException(error); } else { - Log.e("Uber SDK", error); + Log.e(UberSdk.UBER_SDK_LOG_TAG, error); } } } From 6ab807f9d8768de55f2943d5c86a7a068315cf2f Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Wed, 24 Jan 2018 15:06:18 -0800 Subject: [PATCH 041/165] Improving Readme instructions for custom tabs --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a5fc2472..9ed2f4ef 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,18 @@ If you want to provide a more custom experience in your app, there are a few cla ### Login The Uber SDK allows for three login flows: Implicit Grant (local web view), Single Sign On with the Uber App, and Authorization Code Grant (requires a backend to catch the local web view redirect and complete OAuth). -To use Single Sign On you must register a hash of your application's signing certificate in the Application Signature section of the [developer dashboard](https://developer.uber.com/dashboard). + +#### Dashboard configuration +To use SDK features, two configuration details must be set on the Uber Developer Dashboard. + + 1. Sign into to the [developer dashboard](https://developer.uber.com/dashboard) + + 1. Register a redirect URI to be used to communication authentication results. The default used + by the SDK is in the format of `applicationId.uberauth://redirect`. ex: `com.example + .uberauth://redirect`. To configure the SDK to use a different redirect URI, see the steps below. + + 1. To use Single Sign On you must register a hash of your application's signing certificate in the + Application Signature section of the settings page of your application. To get the hash of your signing certificate, run this command with the alias of your key and path to your keystore: @@ -200,7 +211,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data){ } ``` -#### Authentication Migration (Version 0.8 and above) +#### Authentication Migration and setup (Version 0.8 and above) With Version 0.8 and above of the SDK, the redirect URI is more strongly enforced to meet IETF standards [IETF RFC](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12). @@ -232,7 +243,9 @@ filter. ``` -The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if that is unavailable, fallback to Implicit Grant if privileged scopes are not requested, otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager.setRedirectForAuthorizationCode(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). +The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, +and if SSO is unavailable, fallback to Implicit Grant if privileged scopes are not requested, +otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager.setRedirectForAuthorizationCode(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). #### Login Errors Upon a failure to login, an `AuthenticationError` will be provided in the `LoginCallback`. This enum provides a series of values that provide more information on the type of error. From 06ebd6f7e748e3964d05442f3b63579bd48aaa9c Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Mon, 29 Jan 2018 11:50:05 -0800 Subject: [PATCH 042/165] Adding additional error check flag related to auth code flow --- .../main/java/com/uber/sdk/android/core/auth/LoginManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 68fd4d97..083feb7e 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -370,7 +370,8 @@ private void validateRedirectUriRegistration(@NonNull Activity activity) { String generatedRedirectUri = activity.getPackageName().concat(".uberauth://redirect"); String setRedirectUri = sessionConfiguration.getRedirectUri(); - if (setRedirectUri != null && + if (isRedirectForAuthorizationCode() && + setRedirectUri != null && !generatedRedirectUri.equals(setRedirectUri) && !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { String error = "Misconfigured redirect_uri. See https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above " From ec47377f05b6bed66a321dd2f2c9a3b5fc1c2d61 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Mon, 5 Feb 2018 15:27:43 -0800 Subject: [PATCH 043/165] Moving migration logic to better account for auth code flow --- .../sdk/android/core/auth/LoginActivity.java | 5 +- .../sdk/android/core/auth/LoginCallback.java | 2 + .../sdk/android/core/auth/LoginManager.java | 108 +++++++++++--- .../android/core/auth/LoginButtonTest.java | 4 +- .../android/core/auth/LoginManagerTest.java | 137 +++++++++++++----- 5 files changed, 194 insertions(+), 62 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 1a7761ac..b634bc83 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -169,11 +169,8 @@ protected void loadUrl() { String redirectUri = sessionConfiguration.getRedirectUri() != null ? sessionConfiguration .getRedirectUri() : getApplicationContext().getPackageName() + "uberauth"; - boolean registeredRedirectUri = AuthUtils.isRedirectUriRegistered(this, - Uri.parse(redirectUri)); - String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration); - if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false) || !registeredRedirectUri) { + if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)) { loadWebview(url, redirectUri); } else { loadChrometab(url); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java index 5dc556cb..a8bc569b 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java @@ -56,6 +56,8 @@ public interface LoginCallback { * Client Secret, see https://developer.uber.com/docs/authentication#section-step-two-receive-redirect * * @param authorizationCode the authorizationCode that can be used to retrieve {@link AccessToken} + * @deprecated Should use Custom Tab flow with registered Redirect URI */ + @Deprecated void onAuthorizationCodeReceived(@NonNull String authorizationCode); } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 083feb7e..cf9892e1 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -43,6 +43,7 @@ import com.uber.sdk.core.client.SessionConfiguration; import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; +import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; /** * Manages user login via OAuth 2.0 Implicit Grant. Be sure to call @@ -96,8 +97,11 @@ public class LoginManager { private final SessionConfiguration sessionConfiguration; private final int requestCode; + private boolean authCodeFlowEnabled = false; + @Deprecated private boolean redirectForAuthorizationCode = false; + /** * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} @@ -146,6 +150,8 @@ public LoginManager( public void login(@NonNull Activity activity) { checkNotEmpty(sessionConfiguration.getScopes(), "Scopes must be set in the Session " + "Configuration."); + checkNotNull(sessionConfiguration.getRedirectUri(), "Redirect URI must be set in " + + "Session Configuration."); SsoDeeplink ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(sessionConfiguration.getClientId()) @@ -158,7 +164,7 @@ public void login(@NonNull Activity activity) { ssoDeeplink.execute(); } else if (!AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { loginForImplicitGrant(activity); - } else if (redirectForAuthorizationCode) { + } else if (isAuthCodeFlowEnabled()) { loginForAuthorizationCode(activity); } else { redirectToInstallApp(activity); @@ -171,8 +177,11 @@ public void login(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForImplicitGrant(@NonNull Activity activity) { - validateRedirectUriRegistration(activity); - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN); + boolean forceWebView = + isInLegacyRedirectMode(activity); + + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, + ResponseType.TOKEN, forceWebView); activity.startActivityForResult(intent, requestCode); } @@ -182,8 +191,10 @@ public void loginForImplicitGrant(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForAuthorizationCode(@NonNull Activity activity) { - validateRedirectUriRegistration(activity); - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.CODE); + boolean forceWebView = + isInLegacyRedirectMode(activity); + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, + ResponseType.CODE, forceWebView); activity.startActivityForResult(intent, requestCode); } @@ -256,9 +267,12 @@ public boolean isAuthenticated() { * * @param redirectForAuthorizationCode true if should redirect, otherwise false * @return this instance of {@link LoginManager} + * @deprecated, See Authorization Migration guide https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above */ + @Deprecated public LoginManager setRedirectForAuthorizationCode(boolean redirectForAuthorizationCode) { this.redirectForAuthorizationCode = redirectForAuthorizationCode; + setAuthCodeFlowEnabled(true); return this; } @@ -272,11 +286,42 @@ public LoginManager setRedirectForAuthorizationCode(boolean redirectForAuthoriza * update Uber app instead. * * @return true if redirect by default, otherwise false + * @deprecated, See Authorization Migration guide https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above */ + @Deprecated public boolean isRedirectForAuthorizationCode() { return redirectForAuthorizationCode; } + + /** + * Enable the use of the Authorization Code Flow + * (https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an + * installation prompt for the Uber app as a login fallback mechanism. + * + * Requires that the app's backend system is configured to support this flow and the redirect + * URI is pointed correctly. + * + * @param authCodeFlowEnabled true for use of auth code flow, false to fallback to Uber app + * installation + * @return this instace of {@link LoginManager} + */ + public LoginManager setAuthCodeFlowEnabled(boolean authCodeFlowEnabled) { + this.authCodeFlowEnabled = authCodeFlowEnabled; + return this; + } + + /** + * Indicates the use of the Authorization Code Flow + * (https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an + * installation prompt for the Uber app as a login fallback mechanism. + * + * @return true if Auth Code Flow is enabled, otherwise false + */ + public boolean isAuthCodeFlowEnabled() { + return authCodeFlowEnabled; + } + private void redirectToInstallApp(@NonNull Activity activity) { new SignupDeeplink(activity, sessionConfiguration.getClientId(), USER_AGENT).execute(); } @@ -329,7 +374,7 @@ private void handleResultCancelled( loginForImplicitGrant(activity); return; } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) - && redirectForAuthorizationCode) { + && isAuthCodeFlowEnabled()) { loginForAuthorizationCode(activity); } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { AppProtocol appProtocol = new AppProtocol(); @@ -366,26 +411,53 @@ private void handleResultOk(@Nullable Intent data) { } } - private void validateRedirectUriRegistration(@NonNull Activity activity) { - + boolean isInLegacyRedirectMode(@NonNull Activity activity) { String generatedRedirectUri = activity.getPackageName().concat(".uberauth://redirect"); String setRedirectUri = sessionConfiguration.getRedirectUri(); - if (isRedirectForAuthorizationCode() && - setRedirectUri != null && - !generatedRedirectUri.equals(setRedirectUri) - && !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { - String error = "Misconfigured redirect_uri. See https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above " + + if (redirectForAuthorizationCode) { + String message = "The Uber Authentication Flow for the Authorization Code Flow has " + + "been upgraded in 0.8.0 and a redirect URI must now be supplied to the application. " + + "You are seeing this error because the use of deprecated method " + + "LoginManager.setRedirectForAuthorizationCode() indicates your flow may not " + + "support the recent changes. See https://github" + + ".com/uber/rides-android-sdk#authentication-migration-version" + + "-08-and-above for resolution steps" + + "to insure your setup is correct and then migrate to the non-deprecate " + + "method LoginManager.setAuthCodeFlowEnabled()"; + + logOrError(activity, new IllegalStateException(message)); + return true; + } + + if (sessionConfiguration.getRedirectUri() == null) { + String message = "Redirect URI must be set in " + + "Session Configuration."; + + logOrError(activity, new NullPointerException(message)); + return true; + } + + if (!generatedRedirectUri.equals(setRedirectUri) && + !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { + String message = "Misconfigured redirect_uri. See https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above" + "for more info. Either 1) Register " + generatedRedirectUri + " as a " + "redirect uri for the app at https://developer.uber.com/dashboard/ and " + "specify this in your SessionConfiguration or 2) Override the default " + "redirect_uri with the current one set (" + setRedirectUri + ") in the " + "AndroidManifest."; + logOrError(activity, new IllegalStateException(message)); + return true; + } - if (Utility.isDebugable(activity)) { - throw new IllegalStateException(error); - } else { - Log.e(UberSdk.UBER_SDK_LOG_TAG, error); - } + return false; + } + + private void logOrError(Activity activity, Exception exception) { + if (Utility.isDebugable(activity)) { + throw new IllegalStateException(exception); + } else { + Log.e(UberSdk.UBER_SDK_LOG_TAG, exception.getMessage(), exception); } } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index 08fbd45f..7fdc1c71 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -131,7 +131,9 @@ public void testButtonClickWithoutLoginManager_shouldCreateNew() { .build(); loginButton = new LoginButton(activity, attributeSet); - loginButton.setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()); + loginButton.setSessionConfiguration(new SessionConfiguration.Builder() + .setRedirectUri("com.example://redirect") + .setClientId("clientId").build()); loginButton.setCallback(loginCallback); loginButton.setScopes(SCOPES); loginButton.setAccessTokenStorage(accessTokenStorage); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 7b937b27..0f2b767a 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -113,25 +113,46 @@ public class LoginManagerTest extends RobolectricTestBase { @Mock AccessTokenStorage accessTokenStorage; - SessionConfiguration sessionConfiguration; + SessionConfiguration SessionConfigurationWithSetUri; + SessionConfiguration SessionConfigurationWithGeneratedUri; - private LoginManager loginManager; + private LoginManager loginManagerWithSetUri; + private LoginManager loginManagerwithGeneratedUri; + + private ApplicationInfo debuggableApplicationInfo; + private ApplicationInfo releaseApplicationInfo; @Before public void setup() { - sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID).setScopes(MIXED_SCOPES).build(); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); + SessionConfigurationWithGeneratedUri = new SessionConfiguration.Builder().setClientId(CLIENT_ID) + .setRedirectUri("com.example.uberauth://redirect") + .setScopes(MIXED_SCOPES).build(); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithGeneratedUri); + + SessionConfigurationWithSetUri = new SessionConfiguration.Builder().setClientId(CLIENT_ID) + .setRedirectUri("com.custom://redirect") + .setScopes(MIXED_SCOPES).build(); + loginManagerWithSetUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithSetUri); when(activity.getPackageManager()).thenReturn(packageManager); when(activity.getApplicationInfo()).thenReturn(new ApplicationInfo()); when(activity.getPackageName()).thenReturn("com.example"); + + debuggableApplicationInfo = new ApplicationInfo(); + debuggableApplicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE; + releaseApplicationInfo = new ApplicationInfo(); + releaseApplicationInfo.flags = 0; + when(activity.getApplicationInfo()).thenReturn(debuggableApplicationInfo); + } @Test public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); - loginManager.login(activity); + loginManagerwithGeneratedUri.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -144,11 +165,12 @@ public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { @Test public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchIntent() { - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithGeneratedUri, REQUEST_CODE); stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); - loginManager.login(activity); + loginManagerwithGeneratedUri.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -161,12 +183,13 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte @Test public void loginWithoutAppInstalledGeneralScopes_shouldLaunchWebView() { - sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); + SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().setScopes(GENERAL_SCOPES).build(); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithGeneratedUri); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - loginManager.login(activity); + loginManagerwithGeneratedUri.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -185,13 +208,14 @@ public void loginWithoutAppInstalledPrivilegedScopes_shouldLaunchAppInstall() { final Activity activity = spy(Robolectric.setupActivity(Activity.class)); when(activity.getPackageManager()).thenReturn(packageManager); - sessionConfiguration = sessionConfiguration.newBuilder().build(); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration) - .setRedirectForAuthorizationCode(false); + SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().build(); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithGeneratedUri) + .setAuthCodeFlowEnabled(false); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - loginManager.login(activity); + loginManagerwithGeneratedUri.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -209,7 +233,7 @@ public void onActivityResult_whenResultOkAndHasToken_shouldCallbackSuccess() { .putExtra(EXTRA_EXPIRES_IN, ACCESS_TOKEN.getExpiresIn()) .putExtra(EXTRA_TOKEN_TYPE, ACCESS_TOKEN.getTokenType()); - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); ArgumentCaptor storedToken = ArgumentCaptor.forClass(AccessToken.class); ArgumentCaptor returnedToken = ArgumentCaptor.forClass(AccessToken.class); @@ -225,7 +249,7 @@ public void onActivityResult_whenResultOkAndHasCode_shouldCallbackSuccess() { Intent intent = new Intent() .putExtra(EXTRA_CODE_RECEIVED, AUTHORIZATION_CODE); - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); ArgumentCaptor capturedCode = ArgumentCaptor.forClass(String.class); verify(callback).onAuthorizationCodeReceived(capturedCode.capture()); @@ -235,7 +259,7 @@ public void onActivityResult_whenResultOkAndHasCode_shouldCallbackSuccess() { @Test public void onActivityResult_whenResultCanceledAndNoData_shouldCallbackCancel() { - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); verify(callback).onLoginCancel(); } @@ -244,26 +268,26 @@ public void onActivityResult_whenResultCanceledAndHasData_shouldCallbackError() Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.INVALID_RESPONSE .toStandardString()); - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); verify(callback).onLoginError(AuthenticationError.INVALID_RESPONSE); } @Test public void onActivityResult_whenResultCanceledAndNoData_shouldCancel() { - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); verify(callback).onLoginCancel(); } @Test public void onActivityResult_whenResultOkAndNoData_shouldCallbackErrorUnknown() { - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, null); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, null); verify(callback).onLoginError(AuthenticationError.UNKNOWN); } @Test public void onActivityResult_whenRequestCodeDoesNotMatch_nothingShouldHappen() { Intent intent = mock(Intent.class); - loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); verifyZeroInteractions(intent); verifyZeroInteractions(callback); } @@ -271,7 +295,7 @@ public void onActivityResult_whenRequestCodeDoesNotMatch_nothingShouldHappen() { @Test public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShouldHappen() { Intent intent = mock(Intent.class); - loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); verifyZeroInteractions(intent); } @@ -279,8 +303,8 @@ public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShoul public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAuthorizationCode() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - loginManager.setRedirectForAuthorizationCode(true); - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManagerwithGeneratedUri.setAuthCodeFlowEnabled(true); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -299,23 +323,25 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAut @Test public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_shouldError() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - sessionConfiguration = sessionConfiguration.newBuilder().build(); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration) - .setRedirectForAuthorizationCode(false); + SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().build(); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithGeneratedUri) + .setAuthCodeFlowEnabled(false); - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); verify(callback).onLoginError(AuthenticationError.UNAVAILABLE); } @Test public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImplicitGrant() { - sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); + SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().setScopes(GENERAL_SCOPES).build(); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, + SessionConfigurationWithGeneratedUri); Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -334,41 +360,74 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImp @Test public void isAuthenticated_withServerToken_true() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration.newBuilder().setServerToken("serverToken").build()); - assertTrue(loginManager.isAuthenticated()); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, SessionConfigurationWithGeneratedUri + .newBuilder().setServerToken("serverToken").build()); + assertTrue(loginManagerwithGeneratedUri.isAuthenticated()); } @Test public void isAuthenticated_withAccessToken_true() { when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); - assertTrue(loginManager.isAuthenticated()); + assertTrue(loginManagerwithGeneratedUri.isAuthenticated()); } @Test public void isAuthenticated_withoutAccessOrServerToken_false() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - assertFalse(loginManager.isAuthenticated()); + assertFalse(loginManagerwithGeneratedUri.isAuthenticated()); } @Test public void getSession_withServerToken_successful() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration.newBuilder().setServerToken("serverToken").build()); - Session session = loginManager.getSession(); + loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, SessionConfigurationWithGeneratedUri + .newBuilder().setServerToken("serverToken").build()); + Session session = loginManagerwithGeneratedUri.getSession(); assertEquals("serverToken", session.getAuthenticator().getSessionConfiguration().getServerToken()); } @Test public void getSession_withAccessToken_successful() { when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); - Session session = loginManager.getSession(); + Session session = loginManagerwithGeneratedUri.getSession(); assertEquals(ACCESS_TOKEN, ((AccessTokenAuthenticator)session.getAuthenticator()).getTokenStorage().getAccessToken()); } @Test(expected = IllegalStateException.class) public void getSession_withoutAccessTokenOrToken_fails() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - loginManager.getSession(); + loginManagerwithGeneratedUri.getSession(); + } + + @Test(expected = IllegalStateException.class) + public void isInLegacyRedirectMode_whenMismatchingUriInDebug_throwsException() { + loginManagerWithSetUri.isInLegacyRedirectMode(activity); + } + + @Test() + public void isInLegacyRedirectMode_whenMismatchingUriInRelease_logsErrorAndReturnsTrue() { + when(activity.getApplicationInfo()).thenReturn(releaseApplicationInfo); + assertThat(loginManagerWithSetUri.isInLegacyRedirectMode(activity)).isTrue(); + } + + + @Test(expected = IllegalStateException.class) + public void isInLegacyRedirectMode_whenLegacyAuthCodeFlowInDebug_throwsException() { + loginManagerwithGeneratedUri.setRedirectForAuthorizationCode(true); + loginManagerwithGeneratedUri.isInLegacyRedirectMode(activity); + + } + + @Test() + public void isInLegacyRedirectMode_whenLegacyAuthCodeFlowInRelease_logsErrorAndReturnsTrue() { + when(activity.getApplicationInfo()).thenReturn(releaseApplicationInfo); + loginManagerwithGeneratedUri.setRedirectForAuthorizationCode(true); + assertThat(loginManagerwithGeneratedUri.isInLegacyRedirectMode(activity)).isTrue(); + } + + @Test() + public void isInLegacyRedirectMode_whenMatchingUri_returnsFalse() { + assertThat(loginManagerwithGeneratedUri.isInLegacyRedirectMode(activity)).isFalse(); } private static PackageManager stubAppInstalled(PackageManager packageManager, String packageName, int versionCode) { From 4c73f008585d52f4379e18ac8378b35daf9fd09b Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Mon, 5 Feb 2018 17:06:08 -0800 Subject: [PATCH 044/165] Fix Auth Docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ed2f4ef..e0604605 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,9 @@ filter. The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if SSO is unavailable, fallback to Implicit Grant if privileged scopes are not requested, -otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager.setRedirectForAuthorizationCode(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). +otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager +.setAuthCodeEnabled(true)` to prevent the redirect to the Play Store. Implicit Grant will allow +access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). #### Login Errors Upon a failure to login, an `AuthenticationError` will be provided in the `LoginCallback`. This enum provides a series of values that provide more information on the type of error. From 8babeefbbca73cc2bc82c6c8f8bde8259cf145de Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 6 Feb 2018 11:46:08 -0800 Subject: [PATCH 045/165] cleanup --- .../sdk/android/core/auth/LoginManager.java | 9 +---- .../uber/sdk/android/core/utils/Utility.java | 13 +++++++ samples/login-sample/gradle.properties | 4 +- .../android/samples/LoginSampleActivity.java | 38 +++++++++---------- .../src/main/AndroidManifest.xml | 7 ---- 5 files changed, 35 insertions(+), 36 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index cf9892e1..c7bade13 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -42,6 +42,7 @@ import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; +import static com.uber.sdk.android.core.utils.Utility.logOrError; import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; @@ -452,12 +453,4 @@ boolean isInLegacyRedirectMode(@NonNull Activity activity) { return false; } - - private void logOrError(Activity activity, Exception exception) { - if (Utility.isDebugable(activity)) { - throw new IllegalStateException(exception); - } else { - Log.e(UberSdk.UBER_SDK_LOG_TAG, exception.getMessage(), exception); - } - } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java index 25d58c4c..e4b606ed 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java @@ -1,8 +1,12 @@ package com.uber.sdk.android.core.utils; +import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; import android.support.annotation.NonNull; +import android.util.Log; + +import com.uber.sdk.android.core.UberSdk; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -28,6 +32,15 @@ public static boolean isDebugable(@NonNull Context context) { } + + public static void logOrError(Activity activity, Exception exception) { + if (Utility.isDebugable(activity)) { + throw new IllegalStateException(exception); + } else { + Log.e(UberSdk.UBER_SDK_LOG_TAG, exception.getMessage(), exception); + } + } + private static String hashWithAlgorithm(String algorithm, String key) { return hashWithAlgorithm(algorithm, key.getBytes()); } diff --git a/samples/login-sample/gradle.properties b/samples/login-sample/gradle.properties index a1c9d5e1..aa248f2e 100644 --- a/samples/login-sample/gradle.properties +++ b/samples/login-sample/gradle.properties @@ -1,3 +1,3 @@ description=Login to Uber Sample -UBER_CLIENT_ID=insert_your_client_id_here -UBER_REDIRECT_URI=insert_your_redirect_uri_here \ No newline at end of file +#UBER_CLIENT_ID=insert_your_client_id_here +#UBER_REDIRECT_URI=insert_your_redirect_uri_here \ No newline at end of file diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index d66c1144..1123de90 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -104,15 +104,15 @@ protected void onCreate(Bundle savedInstanceState) { //Create a button with a custom request code whiteButton = (LoginButton) findViewById(R.id.uber_button_white); whiteButton.setCallback(new SampleLoginCallback()) - .setSessionConfiguration(configuration); + .setSessionConfiguration(configuration); //Create a button using a custom AccessTokenStorage //Custom Scopes are set using XML for this button as well in R.layout.activity_sample blackButton = (LoginButton) findViewById(R.id.uber_button_black); blackButton.setAccessTokenStorage(accessTokenStorage) - .setCallback(new SampleLoginCallback()) - .setSessionConfiguration(configuration) - .setRequestCode(LOGIN_BUTTON_CUSTOM_REQUEST_CODE); + .setCallback(new SampleLoginCallback()) + .setSessionConfiguration(configuration) + .setRequestCode(LOGIN_BUTTON_CUSTOM_REQUEST_CODE); //Use a custom button with an onClickListener to call the LoginManager directly @@ -184,21 +184,21 @@ private void loadProfileInfo() { service.getUserProfile() .enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Toast.makeText(LoginSampleActivity.this, getString(R.string.greeting, response.body().getFirstName()), Toast.LENGTH_LONG).show(); - } else { - ApiError error = ErrorParser.parseError(response); - Toast.makeText(LoginSampleActivity.this, error.getClientErrors().get(0).getTitle(), Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - - } - }); + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(LoginSampleActivity.this, getString(R.string.greeting, response.body().getFirstName()), Toast.LENGTH_LONG).show(); + } else { + ApiError error = ErrorParser.parseError(response); + Toast.makeText(LoginSampleActivity.this, error.getClientErrors().get(0).getTitle(), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); } @Override diff --git a/samples/request-button-sample/src/main/AndroidManifest.xml b/samples/request-button-sample/src/main/AndroidManifest.xml index 6ea3b5bf..3bb1dfa4 100644 --- a/samples/request-button-sample/src/main/AndroidManifest.xml +++ b/samples/request-button-sample/src/main/AndroidManifest.xml @@ -38,13 +38,6 @@ - - - - - - From 0ad55cdc0e4803a41cd53536c0852f1ef71d4d7f Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Wed, 7 Feb 2018 18:35:25 -0800 Subject: [PATCH 046/165] Moving migration code to seperate class for better testing and easier removal in a number of months --- .../core/auth/LegacyUriRedirectHandler.java | 135 ++++++++++++++++ .../sdk/android/core/auth/LoginManager.java | 86 ++++------ .../uber/sdk/android/core/utils/Utility.java | 12 +- .../auth/LegacyUriRedirectHandlerTest.java | 121 ++++++++++++++ .../android/core/auth/LoginManagerTest.java | 151 ++++++++---------- samples/login-sample/gradle.properties | 4 +- 6 files changed, 357 insertions(+), 152 deletions(-) create mode 100644 core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java create mode 100644 core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java new file mode 100644 index 00000000..5bb4a9b0 --- /dev/null +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java @@ -0,0 +1,135 @@ +package com.uber.sdk.android.core.auth; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v4.util.Pair; +import android.util.Log; + +import com.uber.sdk.android.core.UberSdk; +import com.uber.sdk.android.core.utils.Utility; +import com.uber.sdk.core.client.SessionConfiguration; + +class LegacyUriRedirectHandler { + + enum Mode { + OFF, + OLD_AUTH_CODE_FLOW, + MISSING_REDIRECT, + MISCONFIGURED_URI; + } + + private Mode mode = Mode.OFF; + + /** + * Will validate that the Redirect URI $FIELD_NAME_PREFIXMode is valid {@link Mode#OFF} and return true + * + * If false, then the app should terminate codeflow, this will happen in Debug mode for + * unhandled migration scenarios. + * See https://github.com/uber/rides-android-sdk#authentication-migration-version. + * + * @param activity to lookup package info and launch blocking dialog + * @param loginManager to validate old auth code flow + * @return true if valid, false if invalid + */ + boolean checkValidState(@NonNull Activity activity, @NonNull LoginManager + loginManager) { + initState(activity, loginManager); + + if (isLegacyMode()) { + final Pair titleAndMessage = getLegacyModeMessage(activity, loginManager); + final IllegalStateException exception = new IllegalStateException(titleAndMessage + .second); + Log.e(UberSdk.UBER_SDK_LOG_TAG, titleAndMessage.first, + exception); + + if(Utility.isDebugable(activity)) { + new AlertDialog.Builder(activity) + .setTitle(titleAndMessage.first) + .setMessage(titleAndMessage.second) + .setNeutralButton("Exit", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + throw exception; + } + }).show(); + return false; + } + } + return true; + } + + boolean isLegacyMode() { + return mode != Mode.OFF; + } + + private void initState(@NonNull Activity activity, @NonNull LoginManager loginManager) { + + SessionConfiguration sessionConfiguration = loginManager.getSessionConfiguration(); + boolean redirectForAuthorizationCode = loginManager.isRedirectForAuthorizationCode(); + + String generatedRedirectUri = activity.getPackageName().concat(".uberauth://redirect"); + String setRedirectUri = sessionConfiguration.getRedirectUri(); + + if (redirectForAuthorizationCode) { + mode = Mode.OLD_AUTH_CODE_FLOW; + } else if (sessionConfiguration.getRedirectUri() == null) { + mode = Mode.MISSING_REDIRECT; + } else if (!generatedRedirectUri.equals(setRedirectUri) && + !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { + mode = Mode.MISCONFIGURED_URI; + } else { + mode = Mode.OFF; + } + + } + + + + private Pair getLegacyModeMessage(@NonNull Context context, @NonNull + LoginManager + loginManager) { + + final Pair titleAndMessage; + switch (mode) { + case OLD_AUTH_CODE_FLOW: + titleAndMessage = new Pair<>("Misconfigured SessionConfiguration, see log.", + "The Uber Authentication Flow for the Authorization Code Flow has " + + "been upgraded in 0.8.0 and a redirect URI must now be supplied to the application. " + + "You are seeing this error because the use of deprecated method " + + "LoginManager.setRedirectForAuthorizationCode() indicates your flow may not " + + "support the recent changes. See https://github" + + ".com/uber/rides-android-sdk#authentication-migration-version" + + "-08-and-above for resolution steps" + + "to insure your setup is correct and then migrate to the non-deprecate " + + "method LoginManager.setAuthCodeFlowEnabled()"); + break; + case MISSING_REDIRECT: + titleAndMessage = new Pair<>("Misconfigured SessionConfiguration, see log.", "Redirect URI must be set in " + + "Session Configuration."); + break; + case MISCONFIGURED_URI: + String generatedRedirectUri = context.getPackageName().concat("" + + ".uberauth://redirect"); + String setRedirectUri = loginManager.getSessionConfiguration().getRedirectUri(); + titleAndMessage = new Pair<>("Misconfigured redirect uri, see log.", + "Misconfigured redirect_uri. See https://github" + + ".com/uber/rides-android-sdk#authentication-migration-version-08-and-above" + + "for more info. Either 1) Register " + generatedRedirectUri + " as a " + + "redirect uri for the app at https://developer.uber.com/dashboard/ and " + + "specify this in your SessionConfiguration or 2) Override the default " + + "redirect_uri with the current one set (" + setRedirectUri + ") in the " + + "AndroidManifest."); + break; + default: + titleAndMessage = new Pair<>("Unknown URI Redirect Issue", "Unknown issue, see " + + "log"); + } + + return titleAndMessage; + + } +} diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index c7bade13..3776daf4 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -24,7 +24,6 @@ import android.app.Activity; import android.content.Intent; -import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; @@ -33,7 +32,6 @@ import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; -import com.uber.sdk.android.core.utils.Utility; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; @@ -42,7 +40,6 @@ import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; -import static com.uber.sdk.android.core.utils.Utility.logOrError; import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; @@ -97,12 +94,12 @@ public class LoginManager { private final LoginCallback callback; private final SessionConfiguration sessionConfiguration; private final int requestCode; + private final LegacyUriRedirectHandler legacyUriRedirectHandler; private boolean authCodeFlowEnabled = false; @Deprecated private boolean redirectForAuthorizationCode = false; - /** * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} @@ -137,10 +134,27 @@ public LoginManager( @NonNull LoginCallback loginCallback, @NonNull SessionConfiguration configuration, int requestCode) { + this(accessTokenStorage, loginCallback, configuration, requestCode, new LegacyUriRedirectHandler()); + } + + /** + * @param accessTokenStorage to store access token. + * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. + * @param configuration to provide authentication information + * @param requestCode custom code to use for Activity communication + * @param legacyUriRedirectHandler Used to handle URI Redirect Migration + */ + LoginManager( + @NonNull AccessTokenStorage accessTokenStorage, + @NonNull LoginCallback loginCallback, + @NonNull SessionConfiguration configuration, + int requestCode, + @NonNull LegacyUriRedirectHandler legacyUriRedirectHandler) { this.accessTokenStorage = accessTokenStorage; this.callback = loginCallback; this.sessionConfiguration = configuration; this.requestCode = requestCode; + this.legacyUriRedirectHandler = legacyUriRedirectHandler; } /** @@ -148,12 +162,16 @@ public LoginManager( * * @param activity the activity used to start the {@link LoginActivity}. */ - public void login(@NonNull Activity activity) { + public void login(final @NonNull Activity activity) { checkNotEmpty(sessionConfiguration.getScopes(), "Scopes must be set in the Session " + "Configuration."); checkNotNull(sessionConfiguration.getRedirectUri(), "Redirect URI must be set in " + "Session Configuration."); + if (!legacyUriRedirectHandler.checkValidState(activity, this)) { + return; + } + SsoDeeplink ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(sessionConfiguration.getClientId()) .scopes(sessionConfiguration.getScopes()) @@ -178,11 +196,13 @@ public void login(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForImplicitGrant(@NonNull Activity activity) { - boolean forceWebView = - isInLegacyRedirectMode(activity); + + if (!legacyUriRedirectHandler.checkValidState(activity, this)) { + return; + } Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, - ResponseType.TOKEN, forceWebView); + ResponseType.TOKEN, legacyUriRedirectHandler.isLegacyMode()); activity.startActivityForResult(intent, requestCode); } @@ -192,10 +212,12 @@ public void loginForImplicitGrant(@NonNull Activity activity) { * @param activity to start Activity on. */ public void loginForAuthorizationCode(@NonNull Activity activity) { - boolean forceWebView = - isInLegacyRedirectMode(activity); + if (!legacyUriRedirectHandler.checkValidState(activity, this)) { + return; + } + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, - ResponseType.CODE, forceWebView); + ResponseType.CODE, legacyUriRedirectHandler.isLegacyMode()); activity.startActivityForResult(intent, requestCode); } @@ -411,46 +433,4 @@ private void handleResultOk(@Nullable Intent data) { } } - - boolean isInLegacyRedirectMode(@NonNull Activity activity) { - String generatedRedirectUri = activity.getPackageName().concat(".uberauth://redirect"); - String setRedirectUri = sessionConfiguration.getRedirectUri(); - - if (redirectForAuthorizationCode) { - String message = "The Uber Authentication Flow for the Authorization Code Flow has " - + "been upgraded in 0.8.0 and a redirect URI must now be supplied to the application. " - + "You are seeing this error because the use of deprecated method " - + "LoginManager.setRedirectForAuthorizationCode() indicates your flow may not " - + "support the recent changes. See https://github" - + ".com/uber/rides-android-sdk#authentication-migration-version" - + "-08-and-above for resolution steps" - + "to insure your setup is correct and then migrate to the non-deprecate " - + "method LoginManager.setAuthCodeFlowEnabled()"; - - logOrError(activity, new IllegalStateException(message)); - return true; - } - - if (sessionConfiguration.getRedirectUri() == null) { - String message = "Redirect URI must be set in " - + "Session Configuration."; - - logOrError(activity, new NullPointerException(message)); - return true; - } - - if (!generatedRedirectUri.equals(setRedirectUri) && - !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { - String message = "Misconfigured redirect_uri. See https://github.com/uber/rides-android-sdk#authentication-migration-version-08-and-above" - + "for more info. Either 1) Register " + generatedRedirectUri + " as a " - + "redirect uri for the app at https://developer.uber.com/dashboard/ and " - + "specify this in your SessionConfiguration or 2) Override the default " - + "redirect_uri with the current one set (" + setRedirectUri + ") in the " - + "AndroidManifest."; - logOrError(activity, new IllegalStateException(message)); - return true; - } - - return false; - } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java index e4b606ed..fc364eea 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java @@ -1,10 +1,13 @@ package com.uber.sdk.android.core.utils; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.pm.ApplicationInfo; import android.support.annotation.NonNull; import android.util.Log; +import android.widget.Toast; import com.uber.sdk.android.core.UberSdk; @@ -32,15 +35,6 @@ public static boolean isDebugable(@NonNull Context context) { } - - public static void logOrError(Activity activity, Exception exception) { - if (Utility.isDebugable(activity)) { - throw new IllegalStateException(exception); - } else { - Log.e(UberSdk.UBER_SDK_LOG_TAG, exception.getMessage(), exception); - } - } - private static String hashWithAlgorithm(String algorithm, String key) { return hashWithAlgorithm(algorithm, key.getBytes()); } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java new file mode 100644 index 00000000..5005ac4a --- /dev/null +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java @@ -0,0 +1,121 @@ +package com.uber.sdk.android.core.auth; + +import android.app.Activity; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.core.client.SessionConfiguration; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class LegacyUriRedirectHandlerTest extends RobolectricTestBase { + + @Mock LoginManager loginManager; + @Mock PackageManager packageManager; + @Mock SessionConfiguration sessionConfiguration; + + Activity activity; + LegacyUriRedirectHandler legacyUriRedirectHandler; + + + private ApplicationInfo applicationInfo; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + applicationInfo = new ApplicationInfo(); + applicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE; + activity = spy(Robolectric.setupActivity(Activity.class)); + //applicationInfo.flags = 0; + + when(sessionConfiguration.getRedirectUri()).thenReturn("com.example.uberauth://redirect"); + when(loginManager.getSessionConfiguration()).thenReturn(sessionConfiguration); + when(activity.getApplicationInfo()).thenReturn(applicationInfo); + when(activity.getPackageManager()).thenReturn(packageManager); + when(activity.getPackageName()).thenReturn("com.example"); + + legacyUriRedirectHandler = new LegacyUriRedirectHandler(); + } + + @Test + public void handleInvalidState_withMismatchingUriInDebug_invalidState() throws Exception { + when(sessionConfiguration.getRedirectUri()) + .thenReturn("com.example2.uberauth://redirect-uri"); + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isFalse(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + } + + @Test + public void handleInvalidState_withMismatchingUriInRelease_validState() throws Exception { + when(sessionConfiguration.getRedirectUri()) + .thenReturn("com.example2.uberauth://redirect-uri"); + applicationInfo.flags = 0; + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + } + + @Test + public void handleInvalidState_withLegacyAuthCodeFlowInDebug_invalidState() throws Exception { + when(loginManager.isRedirectForAuthorizationCode()).thenReturn(true); + + assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isFalse(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + } + + @Test + public void handleInvalidState_withLegacyAuthCodeFlowInRelease_validState() throws Exception { + when(loginManager.isRedirectForAuthorizationCode()).thenReturn(true); + applicationInfo.flags = 0; + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + } + + @Test + public void handleInvalidState_withMissingRedirectUriInDebug_invalidState() throws Exception { + when(sessionConfiguration.getRedirectUri()) + .thenReturn(null); + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isFalse(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + } + + @Test + public void handleInvalidState_withMissingRedirectUriInRelease_validState() throws Exception { + when(sessionConfiguration.getRedirectUri()) + .thenReturn(null); + applicationInfo.flags = 0; + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + } + + @Test + public void handleInvalidState_withValidState_validState() throws Exception { + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + } + + @Test + public void isLegacyMode_uninitialized_validState() throws Exception { + assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + } +} \ No newline at end of file diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 0f2b767a..32ddb0a7 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -63,6 +63,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -113,46 +114,51 @@ public class LoginManagerTest extends RobolectricTestBase { @Mock AccessTokenStorage accessTokenStorage; - SessionConfiguration SessionConfigurationWithSetUri; - SessionConfiguration SessionConfigurationWithGeneratedUri; + @Mock LegacyUriRedirectHandler legacyUriRedirectHandler; - private LoginManager loginManagerWithSetUri; - private LoginManager loginManagerwithGeneratedUri; + SessionConfiguration sessionConfiguration; - private ApplicationInfo debuggableApplicationInfo; - private ApplicationInfo releaseApplicationInfo; + private LoginManager loginManager; @Before public void setup() { - SessionConfigurationWithGeneratedUri = new SessionConfiguration.Builder().setClientId(CLIENT_ID) + sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID) .setRedirectUri("com.example.uberauth://redirect") .setScopes(MIXED_SCOPES).build(); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithGeneratedUri); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration, REQUEST_CODE_LOGIN_DEFAULT, + legacyUriRedirectHandler); - SessionConfigurationWithSetUri = new SessionConfiguration.Builder().setClientId(CLIENT_ID) - .setRedirectUri("com.custom://redirect") - .setScopes(MIXED_SCOPES).build(); - loginManagerWithSetUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithSetUri); when(activity.getPackageManager()).thenReturn(packageManager); when(activity.getApplicationInfo()).thenReturn(new ApplicationInfo()); when(activity.getPackageName()).thenReturn("com.example"); + when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(true); + } + + @Test + public void login_withLegacyModeBlocking_shouldNotLogin() { + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); + when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(false); + loginManager.login(activity); + + verify(activity, never()).startActivityForResult(any(Intent.class), anyInt()); + } - debuggableApplicationInfo = new ApplicationInfo(); - debuggableApplicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE; - releaseApplicationInfo = new ApplicationInfo(); - releaseApplicationInfo.flags = 0; - when(activity.getApplicationInfo()).thenReturn(debuggableApplicationInfo); + @Test + public void login_withLegacyModeNotBlocking_shouldLogin() { + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); + when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(true); + loginManager.login(activity); + verify(activity).startActivityForResult(any(Intent.class), anyInt()); } @Test public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); - loginManagerwithGeneratedUri.login(activity); + loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -165,12 +171,12 @@ public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { @Test public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchIntent() { - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithGeneratedUri, REQUEST_CODE); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration, REQUEST_CODE); stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); - loginManagerwithGeneratedUri.login(activity); + loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -183,13 +189,13 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte @Test public void loginWithoutAppInstalledGeneralScopes_shouldLaunchWebView() { - SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithGeneratedUri); + sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - loginManagerwithGeneratedUri.login(activity); + loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -208,14 +214,14 @@ public void loginWithoutAppInstalledPrivilegedScopes_shouldLaunchAppInstall() { final Activity activity = spy(Robolectric.setupActivity(Activity.class)); when(activity.getPackageManager()).thenReturn(packageManager); - SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().build(); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithGeneratedUri) + sessionConfiguration = sessionConfiguration.newBuilder().build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration) .setAuthCodeFlowEnabled(false); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - loginManagerwithGeneratedUri.login(activity); + loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -233,7 +239,7 @@ public void onActivityResult_whenResultOkAndHasToken_shouldCallbackSuccess() { .putExtra(EXTRA_EXPIRES_IN, ACCESS_TOKEN.getExpiresIn()) .putExtra(EXTRA_TOKEN_TYPE, ACCESS_TOKEN.getTokenType()); - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); ArgumentCaptor storedToken = ArgumentCaptor.forClass(AccessToken.class); ArgumentCaptor returnedToken = ArgumentCaptor.forClass(AccessToken.class); @@ -249,7 +255,7 @@ public void onActivityResult_whenResultOkAndHasCode_shouldCallbackSuccess() { Intent intent = new Intent() .putExtra(EXTRA_CODE_RECEIVED, AUTHORIZATION_CODE); - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, intent); ArgumentCaptor capturedCode = ArgumentCaptor.forClass(String.class); verify(callback).onAuthorizationCodeReceived(capturedCode.capture()); @@ -259,7 +265,7 @@ public void onActivityResult_whenResultOkAndHasCode_shouldCallbackSuccess() { @Test public void onActivityResult_whenResultCanceledAndNoData_shouldCallbackCancel() { - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); verify(callback).onLoginCancel(); } @@ -268,26 +274,26 @@ public void onActivityResult_whenResultCanceledAndHasData_shouldCallbackError() Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.INVALID_RESPONSE .toStandardString()); - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); verify(callback).onLoginError(AuthenticationError.INVALID_RESPONSE); } @Test public void onActivityResult_whenResultCanceledAndNoData_shouldCancel() { - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, null); verify(callback).onLoginCancel(); } @Test public void onActivityResult_whenResultOkAndNoData_shouldCallbackErrorUnknown() { - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, null); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_OK, null); verify(callback).onLoginError(AuthenticationError.UNKNOWN); } @Test public void onActivityResult_whenRequestCodeDoesNotMatch_nothingShouldHappen() { Intent intent = mock(Intent.class); - loginManagerwithGeneratedUri.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); + loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); verifyZeroInteractions(intent); verifyZeroInteractions(callback); } @@ -295,7 +301,7 @@ public void onActivityResult_whenRequestCodeDoesNotMatch_nothingShouldHappen() { @Test public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShouldHappen() { Intent intent = mock(Intent.class); - loginManagerwithGeneratedUri.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); + loginManager.onActivityResult(activity, 1337, Activity.RESULT_OK, intent); verifyZeroInteractions(intent); } @@ -303,8 +309,8 @@ public void onActivityResult_whenResultCanceledAndDataButNoCallback_nothingShoul public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAuthorizationCode() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - loginManagerwithGeneratedUri.setAuthCodeFlowEnabled(true); - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManager.setAuthCodeFlowEnabled(true); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -323,25 +329,25 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAut @Test public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_shouldError() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().build(); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithGeneratedUri) + sessionConfiguration = sessionConfiguration.newBuilder().build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration) .setAuthCodeFlowEnabled(false); - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); verify(callback).onLoginError(AuthenticationError.UNAVAILABLE); } @Test public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImplicitGrant() { - SessionConfigurationWithGeneratedUri = SessionConfigurationWithGeneratedUri.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, - SessionConfigurationWithGeneratedUri); + sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration); Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - loginManagerwithGeneratedUri.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -360,74 +366,43 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImp @Test public void isAuthenticated_withServerToken_true() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, SessionConfigurationWithGeneratedUri + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration .newBuilder().setServerToken("serverToken").build()); - assertTrue(loginManagerwithGeneratedUri.isAuthenticated()); + assertTrue(loginManager.isAuthenticated()); } @Test public void isAuthenticated_withAccessToken_true() { when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); - assertTrue(loginManagerwithGeneratedUri.isAuthenticated()); + assertTrue(loginManager.isAuthenticated()); } @Test public void isAuthenticated_withoutAccessOrServerToken_false() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - assertFalse(loginManagerwithGeneratedUri.isAuthenticated()); + assertFalse(loginManager.isAuthenticated()); } @Test public void getSession_withServerToken_successful() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - loginManagerwithGeneratedUri = new LoginManager(accessTokenStorage, callback, SessionConfigurationWithGeneratedUri + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration .newBuilder().setServerToken("serverToken").build()); - Session session = loginManagerwithGeneratedUri.getSession(); + Session session = loginManager.getSession(); assertEquals("serverToken", session.getAuthenticator().getSessionConfiguration().getServerToken()); } @Test public void getSession_withAccessToken_successful() { when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); - Session session = loginManagerwithGeneratedUri.getSession(); + Session session = loginManager.getSession(); assertEquals(ACCESS_TOKEN, ((AccessTokenAuthenticator)session.getAuthenticator()).getTokenStorage().getAccessToken()); } @Test(expected = IllegalStateException.class) public void getSession_withoutAccessTokenOrToken_fails() { when(accessTokenStorage.getAccessToken()).thenReturn(null); - loginManagerwithGeneratedUri.getSession(); - } - - @Test(expected = IllegalStateException.class) - public void isInLegacyRedirectMode_whenMismatchingUriInDebug_throwsException() { - loginManagerWithSetUri.isInLegacyRedirectMode(activity); - } - - @Test() - public void isInLegacyRedirectMode_whenMismatchingUriInRelease_logsErrorAndReturnsTrue() { - when(activity.getApplicationInfo()).thenReturn(releaseApplicationInfo); - assertThat(loginManagerWithSetUri.isInLegacyRedirectMode(activity)).isTrue(); - } - - - @Test(expected = IllegalStateException.class) - public void isInLegacyRedirectMode_whenLegacyAuthCodeFlowInDebug_throwsException() { - loginManagerwithGeneratedUri.setRedirectForAuthorizationCode(true); - loginManagerwithGeneratedUri.isInLegacyRedirectMode(activity); - - } - - @Test() - public void isInLegacyRedirectMode_whenLegacyAuthCodeFlowInRelease_logsErrorAndReturnsTrue() { - when(activity.getApplicationInfo()).thenReturn(releaseApplicationInfo); - loginManagerwithGeneratedUri.setRedirectForAuthorizationCode(true); - assertThat(loginManagerwithGeneratedUri.isInLegacyRedirectMode(activity)).isTrue(); - } - - @Test() - public void isInLegacyRedirectMode_whenMatchingUri_returnsFalse() { - assertThat(loginManagerwithGeneratedUri.isInLegacyRedirectMode(activity)).isFalse(); + loginManager.getSession(); } private static PackageManager stubAppInstalled(PackageManager packageManager, String packageName, int versionCode) { diff --git a/samples/login-sample/gradle.properties b/samples/login-sample/gradle.properties index aa248f2e..a1c9d5e1 100644 --- a/samples/login-sample/gradle.properties +++ b/samples/login-sample/gradle.properties @@ -1,3 +1,3 @@ description=Login to Uber Sample -#UBER_CLIENT_ID=insert_your_client_id_here -#UBER_REDIRECT_URI=insert_your_redirect_uri_here \ No newline at end of file +UBER_CLIENT_ID=insert_your_client_id_here +UBER_REDIRECT_URI=insert_your_redirect_uri_here \ No newline at end of file From 01bf6740c1c6723db7fbb4a8c745bba4f5b9b552 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Wed, 7 Feb 2018 18:37:30 -0800 Subject: [PATCH 047/165] Fix typo --- .../sdk/android/core/auth/LegacyUriRedirectHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java index 5bb4a9b0..af9401f6 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java @@ -13,6 +13,12 @@ import com.uber.sdk.android.core.utils.Utility; import com.uber.sdk.core.client.SessionConfiguration; +/** + * Manages migration problems from old style of redirect URI handling to newer version for Custom + * tabs support. + * + * See https://github.com/uber/rides-android-sdk#authentication-migration-version. + */ class LegacyUriRedirectHandler { enum Mode { @@ -25,7 +31,7 @@ enum Mode { private Mode mode = Mode.OFF; /** - * Will validate that the Redirect URI $FIELD_NAME_PREFIXMode is valid {@link Mode#OFF} and return true + * Will validate that the Redirect URI mode is valid {@link Mode#OFF} and return true * * If false, then the app should terminate codeflow, this will happen in Debug mode for * unhandled migration scenarios. From 340dca0e46c511759de176c6b4d297695e36b9ef Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 8 Feb 2018 17:48:09 -0800 Subject: [PATCH 048/165] Fixed auth code flow bug, added more tests, better docs --- README.md | 40 +++++- .../core/auth/LegacyUriRedirectHandler.java | 68 ++++----- .../uber/sdk/android/core/utils/Utility.java | 32 ++++- .../main/res/values/strings_unlocalized.xml | 26 ++++ .../auth/LegacyUriRedirectHandlerTest.java | 130 ++++++++++++++++-- 5 files changed, 248 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index e0604605..3fe659eb 100644 --- a/README.md +++ b/README.md @@ -216,10 +216,14 @@ With Version 0.8 and above of the SDK, the redirect URI is more strongly enforce standards [IETF RFC](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12). The SDK will automatically created a redirect URI to be used in the oauth callbacks with -the format "applicationId.uberauth", ex "com.example.uberauth". If this differs from the previous -specified redirect URI configured in the SessionConfiguration, there are two options. +the format "applicationId.uberauth", ex "com.example.uberauth". **This URI must be registered in +the [developer dashboard](https://developer.uber.com/dashboard)** - 1. Change the redirect URI to match the new scheme in the configuration of the Session. If this is left out entirely, the default will be used. +If this differs from the previous specified redirect URI configured in the SessionConfiguration, +there are a few options. + + 1. Change the redirect URI to match the new scheme in the configuration of the Session. If this + is left out entirely, the default will be used. ```java SessionConfiguration config = new SessionConfiguration.Builder() @@ -228,7 +232,7 @@ SessionConfiguration config = new SessionConfiguration.Builder() ``` 2. Override the LoginRedirectReceiverActivity in your main manifest and provide a custom intent -filter. +filter. Register this custom URI in the developer dashboard for your application. ```xml - + ``` +3. If using [Authorization Code Flow](https://developer.uber.com/docs/riders/guides/authentication/user-access-token), you will need to configure your server to redirect to + the Mobile Application with an access token either via the generated URI or a custom URI as defined in steps 1 and 2. + +The Session should be configured to redirect to the server to do a code exchange and the login +manager should indicate the SDK is operating in the Authorization Code Flow. + +```java +SessionConfiguration config = new SessionConfiguration.Builder() + .setRedirectUri("example.com/redirect") //Where this is your configured server + .build(); + +loginManager.setAuthCodeEnabled(true); +loginManager.login(this); + +``` + + Once the code is exchanged, the server should redirect to a URI in the standard OAUTH format of + `com.example.uberauth://redirect#access_token=ACCESS_TOKEN&token_type=Bearer&expires_in=TTL&scope=SCOPES` + for the SDK to receive the access token and continue operation.`` + + +##### Authorization Code Flow + + The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if SSO is unavailable, fallback to Implicit Grant if privileged scopes are not requested, otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager .setAuthCodeEnabled(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). + #### Login Errors Upon a failure to login, an `AuthenticationError` will be provided in the `LoginCallback`. This enum provides a series of values that provide more information on the type of error. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java index af9401f6..8ec61f17 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java @@ -1,15 +1,12 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.net.Uri; import android.support.annotation.NonNull; import android.support.v4.util.Pair; -import android.util.Log; -import com.uber.sdk.android.core.UberSdk; +import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.utils.Utility; import com.uber.sdk.core.client.SessionConfiguration; @@ -23,9 +20,9 @@ class LegacyUriRedirectHandler { enum Mode { OFF, - OLD_AUTH_CODE_FLOW, + MISCONFIGURED_AUTH_CODE_FLOW, MISSING_REDIRECT, - MISCONFIGURED_URI; + MISMATCHING_URI; } private Mode mode = Mode.OFF; @@ -44,28 +41,16 @@ enum Mode { boolean checkValidState(@NonNull Activity activity, @NonNull LoginManager loginManager) { initState(activity, loginManager); - + boolean validToContinueExecution = true; if (isLegacyMode()) { - final Pair titleAndMessage = getLegacyModeMessage(activity, loginManager); - final IllegalStateException exception = new IllegalStateException(titleAndMessage - .second); - Log.e(UberSdk.UBER_SDK_LOG_TAG, titleAndMessage.first, - exception); - - if(Utility.isDebugable(activity)) { - new AlertDialog.Builder(activity) - .setTitle(titleAndMessage.first) - .setMessage(titleAndMessage.second) - .setNeutralButton("Exit", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - throw exception; - } - }).show(); - return false; - } + String logMessage = activity.getString(getLegacyModeErrorMessage()); + String uiTitle = activity.getString(R.string.ub__misconfigured_redirect_uri_title); + String uiMessage = activity.getString(R.string.ub__misconfigured_redirect_uri_message); + + validToContinueExecution = !Utility.logAndShowBlockingDebugUIAlert(activity, + logMessage, uiTitle, uiMessage, new IllegalStateException(logMessage)); } - return true; + return validToContinueExecution; } boolean isLegacyMode() { @@ -81,12 +66,13 @@ private void initState(@NonNull Activity activity, @NonNull LoginManager loginMa String setRedirectUri = sessionConfiguration.getRedirectUri(); if (redirectForAuthorizationCode) { - mode = Mode.OLD_AUTH_CODE_FLOW; + mode = Mode.MISCONFIGURED_AUTH_CODE_FLOW; } else if (sessionConfiguration.getRedirectUri() == null) { mode = Mode.MISSING_REDIRECT; } else if (!generatedRedirectUri.equals(setRedirectUri) && - !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri))) { - mode = Mode.MISCONFIGURED_URI; + !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri)) && + !loginManager.isAuthCodeFlowEnabled()) { + mode = Mode.MISMATCHING_URI; } else { mode = Mode.OFF; } @@ -101,8 +87,8 @@ private Pair getLegacyModeMessage(@NonNull Context context, @Non final Pair titleAndMessage; switch (mode) { - case OLD_AUTH_CODE_FLOW: - titleAndMessage = new Pair<>("Misconfigured SessionConfiguration, see log.", + case MISCONFIGURED_AUTH_CODE_FLOW: + titleAndMessage = new Pair<>("Misconfigured Redirect URI - See log.", "The Uber Authentication Flow for the Authorization Code Flow has " + "been upgraded in 0.8.0 and a redirect URI must now be supplied to the application. " + "You are seeing this error because the use of deprecated method " @@ -114,14 +100,15 @@ private Pair getLegacyModeMessage(@NonNull Context context, @Non + "method LoginManager.setAuthCodeFlowEnabled()"); break; case MISSING_REDIRECT: - titleAndMessage = new Pair<>("Misconfigured SessionConfiguration, see log.", "Redirect URI must be set in " + titleAndMessage = new Pair<>("Null Redirect URI - See log.", + "Redirect URI must be set in " + "Session Configuration."); break; - case MISCONFIGURED_URI: + case MISMATCHING_URI: String generatedRedirectUri = context.getPackageName().concat("" + ".uberauth://redirect"); String setRedirectUri = loginManager.getSessionConfiguration().getRedirectUri(); - titleAndMessage = new Pair<>("Misconfigured redirect uri, see log.", + titleAndMessage = new Pair<>("Misconfigured Redirect URI - See log.", "Misconfigured redirect_uri. See https://github" + ".com/uber/rides-android-sdk#authentication-migration-version-08-and-above" + "for more info. Either 1) Register " + generatedRedirectUri + " as a " @@ -138,4 +125,17 @@ private Pair getLegacyModeMessage(@NonNull Context context, @Non return titleAndMessage; } + + private int getLegacyModeErrorMessage() { + switch (mode) { + case MISCONFIGURED_AUTH_CODE_FLOW: + return R.string.ub__misconfigured_auth_code_flow_log; + case MISSING_REDIRECT: + return R.string.ub__missing_redirect_uri_log; + case MISMATCHING_URI: + return R.string.ub__mismatching_redirect_uri_log; + default: + return 0; + } + } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java index fc364eea..6ccade0f 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java @@ -7,8 +7,8 @@ import android.content.pm.ApplicationInfo; import android.support.annotation.NonNull; import android.util.Log; -import android.widget.Toast; +import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.UberSdk; import java.security.MessageDigest; @@ -35,6 +35,36 @@ public static boolean isDebugable(@NonNull Context context) { } + /** + * Logs error and when debug is enabled, shows Alert Dialog with debug instructions. + * + * @param activity + * @param logMessage + * @param alertTitle + * @param alertMessage + * @return true if developer error is shown, false otherwise. + */ + public static boolean logAndShowBlockingDebugUIAlert(@NonNull Activity activity, + final @NonNull String logMessage, + final @NonNull String alertTitle, + final @NonNull String alertMessage, + final @NonNull RuntimeException exception) { + Log.e(UberSdk.UBER_SDK_LOG_TAG, logMessage, exception); + + if(Utility.isDebugable(activity)) { + new AlertDialog.Builder(activity) + .setTitle(alertTitle) + .setMessage(alertMessage) + .setNeutralButton(R.string.ub__alert_dialog_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }).show(); + return true; + } + return false; + } + private static String hashWithAlgorithm(String algorithm, String key) { return hashWithAlgorithm(algorithm, key.getBytes()); } diff --git a/core-android/src/main/res/values/strings_unlocalized.xml b/core-android/src/main/res/values/strings_unlocalized.xml index 7a7b0b7e..7924bce2 100644 --- a/core-android/src/main/res/values/strings_unlocalized.xml +++ b/core-android/src/main/res/values/strings_unlocalized.xml @@ -25,4 +25,30 @@ xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> https://m.uber.com/sign-up?client_id=%1$s&user-agent=%2$s + + OK + Misconfigured Redirect URI + + An invalid use of the redirect URI for + authentication has been detected from an older version of the Uber SDK. + \n\n + Read https://github.com/uber/rides-android-sdk#authentication-migration-version for migration + information. See logcat for more details. + + + + LoginManager.setRedirectForAuthorizationCode() is deprecated in versions > 0.8.0. + \n\n + See https://github.com/uber/rides-android-sdk#authentication-migration-version for + information on using LoginManager.setAuthCodeFlowEnabled() with a properly registered URI + for com.uber.sdk.android.core.auth.LoginRedirectReceiverActivity in the AndroidManifest.xml. + + Redirect URI must be set in Session Configuration. + + Redirect URI set in SessionConfiguration does not match URI registered in + AndroidManifest.xml for com.uber.sdk.android.core.auth.LoginRedirectReceiverActivity. + \n\n + See https://github.com/uber/rides-android-sdk#authentication-migration-version for + configuration required in versions > 0.8.0. + diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java index 5005ac4a..38a77e12 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java @@ -1,21 +1,36 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; +import android.app.AlertDialog; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import com.uber.sdk.android.core.BuildConfig; +import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowAlertDialog; +import org.robolectric.shadows.ShadowLog; + +import java.util.List; +import java.util.logging.LogManager; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; public class LegacyUriRedirectHandlerTest extends RobolectricTestBase { @@ -26,6 +41,13 @@ public class LegacyUriRedirectHandlerTest extends RobolectricTestBase { Activity activity; LegacyUriRedirectHandler legacyUriRedirectHandler; + String misconfiguredAuthCode; + String missingRedirectUri; + String mismatchingRedirectUri; + + String alertTitle; + String alertMessage; + private ApplicationInfo applicationInfo; @@ -45,20 +67,36 @@ public void setup() { when(activity.getPackageName()).thenReturn("com.example"); legacyUriRedirectHandler = new LegacyUriRedirectHandler(); + + misconfiguredAuthCode = RuntimeEnvironment.application.getString( + R.string.ub__misconfigured_auth_code_flow_log); + missingRedirectUri = RuntimeEnvironment.application.getString( + R.string.ub__missing_redirect_uri_log); + mismatchingRedirectUri = RuntimeEnvironment.application.getString( + R.string.ub__mismatching_redirect_uri_log); + + alertTitle = RuntimeEnvironment.application.getString( + R.string.ub__misconfigured_redirect_uri_title); + alertMessage = RuntimeEnvironment.application.getString( + R.string.ub__misconfigured_redirect_uri_message); } @Test - public void handleInvalidState_withMismatchingUriInDebug_invalidState() throws Exception { + public void handleInvalidState_withMismatchingUriInDebug_invalidStateWithAlertDialog() { when(sessionConfiguration.getRedirectUri()) .thenReturn("com.example2.uberauth://redirect-uri"); assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isFalse(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + + assertDialogShown(); + assertLastLog(mismatchingRedirectUri); + } @Test - public void handleInvalidState_withMismatchingUriInRelease_validState() throws Exception { + public void handleInvalidState_withMismatchingUriInRelease_validStateWithLog() { when(sessionConfiguration.getRedirectUri()) .thenReturn("com.example2.uberauth://redirect-uri"); applicationInfo.flags = 0; @@ -66,38 +104,50 @@ public void handleInvalidState_withMismatchingUriInRelease_validState() throws E assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isTrue(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + + assertNoDialogShown(); + assertLastLog(mismatchingRedirectUri); } @Test - public void handleInvalidState_withLegacyAuthCodeFlowInDebug_invalidState() throws Exception { + public void handleInvalidState_withLegacyAuthCodeFlowInDebug_invalidStatWithAlertDialog() { when(loginManager.isRedirectForAuthorizationCode()).thenReturn(true); assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isFalse(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + + assertDialogShown(); + assertLastLog(misconfiguredAuthCode); } @Test - public void handleInvalidState_withLegacyAuthCodeFlowInRelease_validState() throws Exception { + public void handleInvalidState_withLegacyAuthCodeFlowInRelease_validState() { when(loginManager.isRedirectForAuthorizationCode()).thenReturn(true); applicationInfo.flags = 0; assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isTrue(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + + assertNoDialogShown(); + assertLastLog(misconfiguredAuthCode); } @Test - public void handleInvalidState_withMissingRedirectUriInDebug_invalidState() throws Exception { + public void handleInvalidState_withMissingRedirectUriInDebug_invalidStateWithAlertDialog() { when(sessionConfiguration.getRedirectUri()) .thenReturn(null); assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isFalse(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + + assertDialogShown(); + assertLastLog(missingRedirectUri); } @Test - public void handleInvalidState_withMissingRedirectUriInRelease_validState() throws Exception { + public void handleInvalidState_withMissingRedirectUriInRelease_validState() { when(sessionConfiguration.getRedirectUri()) .thenReturn(null); applicationInfo.flags = 0; @@ -105,17 +155,81 @@ public void handleInvalidState_withMissingRedirectUriInRelease_validState() thro assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isTrue(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isTrue(); + + assertNoDialogShown(); + assertLastLog(missingRedirectUri); + } + + @Test + public void handleInvalidState_withMatchingRedirectUriAndNoLegacyAuthCodeFlow_validState() { + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + + assertNoDialogShown(); + assertNoLogs(); + } + + @Test + public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInDebug_validState() { + when(sessionConfiguration.getRedirectUri()) + .thenReturn("com.example2.uberauth://redirect-uri"); + when(loginManager.isAuthCodeFlowEnabled()).thenReturn(true); + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + + assertNoDialogShown(); + assertNoLogs(); } @Test - public void handleInvalidState_withValidState_validState() throws Exception { + public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInRelease_validState() { + when(sessionConfiguration.getRedirectUri()) + .thenReturn("com.example2.uberauth://redirect-uri"); + when(loginManager.isAuthCodeFlowEnabled()).thenReturn(true); + applicationInfo.flags = 0; + assertThat(legacyUriRedirectHandler.checkValidState(activity, loginManager)).isTrue(); assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + + assertNoDialogShown(); + assertNoLogs(); } @Test - public void isLegacyMode_uninitialized_validState() throws Exception { + public void isLegacyMode_uninitialized_validState() { assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + + assertNoDialogShown(); + assertNoLogs(); + } + + private void assertLastLog(String message) { + List logItemList = ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG); + assertThat(ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG)).isNotEmpty(); + ShadowLog.LogItem logItem = logItemList.get(logItemList.size()-1); + assertThat(logItem.msg).isEqualTo(message); + } + + private void assertNoLogs() { + List logItemList = ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG); + assertThat(ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG)).isNull(); + } + + private void assertDialogShown() { + AlertDialog alertDialog = ShadowAlertDialog.getLatestAlertDialog(); + assertThat(alertDialog.isShowing()).isTrue(); + assertThat(shadowOf(alertDialog).getTitle()) + .isEqualTo(alertTitle); + assertThat(shadowOf(alertDialog).getMessage()) + .isEqualTo(alertMessage); + } + + private void assertNoDialogShown() { + AlertDialog alertDialog = ShadowAlertDialog.getLatestAlertDialog(); + assertThat(alertDialog).isNull(); } } \ No newline at end of file From 8ca1c598a11040991075b2a7c2d292885648d42b Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 9 Feb 2018 08:23:03 -0800 Subject: [PATCH 049/165] [Gradle Release Plugin] - pre tag commit: 'v0.8.0'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac965f29..558ba3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.8.0 - TBD +v0.8.0 - 02/09/2018 ------------ ### Changed diff --git a/gradle.properties b/gradle.properties index e3604358..67bdaa33 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Fri, 17 Nov 2017 15:37:20 -0800 +#Fri, 09 Feb 2018 08:19:16 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.8.0-SNAPSHOT -VERSION_NAME=0.8.0-SNAPSHOT +version=0.8.0 +VERSION_NAME=0.8.0 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 899a70b995e02a53ac805ff3d5daf389b55903e7 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 9 Feb 2018 08:23:14 -0800 Subject: [PATCH 050/165] [Gradle Release Plugin] - new version commit: 'v0.8.1-SNAPSHOT'. --- CHANGELOG.md | 3 +++ gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 558ba3a8..eb70a856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.8.1 - TBD +------------ + v0.8.0 - 02/09/2018 ------------ diff --git a/gradle.properties b/gradle.properties index 67bdaa33..eefaa1c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Fri, 09 Feb 2018 08:19:16 -0800 +#Fri, 09 Feb 2018 08:23:14 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.8.0 -VERSION_NAME=0.8.0 +version=0.8.1-SNAPSHOT +VERSION_NAME=0.8.1-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 5b10d4c4308da9407ad0af0eab0cb1fa43a31553 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 8 Feb 2018 22:45:26 -0800 Subject: [PATCH 051/165] Add support for mobile web with deprecated ride widget --- CHANGELOG.md | 2 + README.md | 112 +++++---- .../com/uber/sdk/android/core/Deeplink.java | 8 + .../sdk/android/core/auth/SsoDeeplink.java | 2 +- .../sdk/android/core/utils/AppProtocol.java | 28 ++- .../sdk/android/rides/RequestDeeplink.java | 182 +------------- .../rides/RequestDeeplinkBehavior.java | 11 +- .../sdk/android/rides/RideParameters.java | 2 +- .../rides/RideRequestActivityBehavior.java | 4 + .../android/rides/RideRequestBehavior.java | 3 + .../sdk/android/rides/RideRequestButton.java | 41 +++- .../android/rides/RideRequestDeeplink.java | 232 ++++++++++++++++++ .../sdk/android/rides/RideRequestView.java | 6 +- ...Test.java => RideRequestDeeplinkTest.java} | 141 ++++++++--- .../mobile_web_just_client_provided | 1 + .../mobile_web_ul_just_client_provided | 1 + .../android/rides/samples/SampleActivity.java | 23 -- 17 files changed, 490 insertions(+), 309 deletions(-) create mode 100644 rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java rename rides-android/src/test/java/com/uber/sdk/android/rides/{RequestDeeplinkTest.java => RideRequestDeeplinkTest.java} (55%) create mode 100644 rides-android/src/test/resources/deeplinkuris/mobile_web_just_client_provided create mode 100644 rides-android/src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided diff --git a/CHANGELOG.md b/CHANGELOG.md index eb70a856..0d1c905f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ v0.8.0 - 02/09/2018 ### Added - [Issue #22](https://github.com/uber/rides-android-sdk/issues/22) Customtab support + - [Issue #111](https://github.com/uber/rides-android-sdk/issues/111) Add Uber Mobile Web support + over deprecated Ride Request Widget v0.7.0 - 11/17/2017 diff --git a/README.md b/README.md index 3fe659eb..ae54c4b2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Uber Rides Android SDK (beta) [![Build Status](https://travis-ci.org/uber/rides-android-sdk.svg?branch=master)](https://travis-ci.org/uber/rides-android-sdk) -Official Android SDK (beta) to support: +Official Android SDK to support: - Ride Request Button - - Ride Request Widget + - RIde Request Deeplinks + - Uber Authentication - REST APIs -At a minimum, this SDK is designed to work with Android SDK 14. +At a minimum, this SDK is designed to work with Android SDK 15. ## Before you begin @@ -30,18 +31,61 @@ dependencies { In order for the SDK to function correctly, you need to add some information about your app. In your application, create a `SessionConfiguration` to use with the various components of the library. If you prefer the set it and forget it model, use the `UberSdk` class to initialize with a default `SessionConfiguration`. ```java - SessionConfiguration config = new SessionConfiguration.Builder() .setClientId("YOUR_CLIENT_ID") //This is necessary .setRedirectUri("YOUR_REDIRECT_URI") //This is necessary if you'll be using implicit grant .setEnvironment(Environment.SANDBOX) //Useful for testing your app in the sandbox environment .setScopes(Arrays.asList(Scope.PROFILE, Scope.RIDE_WIDGETS)) //Your scopes for authentication here .build(); +``` +## Ride Request Deeplink +The Ride Request Deeplink provides an easy to use method to provide ride functionality against +the install Uber app or the mobile web experience. + + +Without any extra configuration, the `RideRequestDeeplink` will deeplink to the Uber app. 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: -//This is a convenience method and will set the default config to be used in other components without passing it directly. -UberSdk.initialize(config); +```java +RideParameters rideParams = new RideParameters.Builder() + .setProductId("a1111c8c-c720-46c3-8534-2fcdd730040d") + .setPickupLocation(37.775304, -122.417522, "Uber HQ", "1455 Market Street, San Francisco") + .setDropoffLocation(37.795079, -122.4397805, "Embarcadero", "One Embarcadero Center, San Francisco") + .build(); +requestButton.setRideParameters(rideParams); ``` +After configuring the Ride Parameters, pass them into the `RideRequestDeeplink` builder object to + construct and execute the deeplink. + +```java +RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setSessionConfiguration(config)) + .setRideParameters(rideParameters) + .build(); + deeplink.execute(); + +``` + +### Deeplink Fallbacks +The Ride Request Deeplink will prefer to use deferred deeplinking by default, where the user is +taken to the Play Store to download the app, and then continue the deeplink behavior in the app +after installation. However, an alternate fallback may be used to prefer the mobile web +experience instead. + +To prefer mobile web over an app installation, set the fallback on the builder: + +```java +RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setSessionConfiguration(config) + .setFallback(Deeplink.Fallback.MOBILE_WEB) + .setRideParameters(rideParameters) + .build(); + deeplink.execute(); + +``` + + ## Ride Request Button The `RideRequestButton` offers the quickest ways to integrate Uber into your application. You can add a Ride Request Button to your View like you would any other View: @@ -49,7 +93,7 @@ The `RideRequestButton` offers the quickest ways to integrate Uber into your app RideRequestButton requestButton = new RideRequestButton(context); layout.addView(requestButton); ``` -This will create a request button with default deeplinking behavior, with the 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. +This will create a request button with deeplinking behavior, with the 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 @@ -84,32 +128,8 @@ For a button with a white background and black text: uber:ub__style="white"/> ``` -### Deep linking parameters -Without any extra configuration, the `RideRequestButton` will deeplink to the Uber app. 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 -RideParameters rideParams = new RideParameters.Builder() - .setProductId("a1111c8c-c720-46c3-8534-2fcdd730040d") - .setPickupLocation(37.775304, -122.417522, "Uber HQ", "1455 Market Street, San Francisco") - .setDropoffLocation(37.795079, -122.4397805, "Embarcadero", "One Embarcadero Center, San Francisco") - .build(); -requestButton.setRideParameters(rideParams); -``` - With all the necessary parameters set, pressing the button will seamlessly prompt a ride request confirmation screen. -## Ride Request Widget -The Uber Rides SDK provides a simple way to integrate the Ride Request View using the `RideRequestButton` via the `RideRequestActivityBehavior`. The button can be configured with this behavior object to show the `RideRequestActivity` on click, rather than deeplinking to the Uber app. Without any ride parameters, it will attempt to use the user's current location for pickup - for this, you must ask your user for location permissions. Otherwise, any pickup/dropoff location information passed via `RideParameters` to the button will be pre-filled in the Ride Request View. - -```java -// The REQUEST_CODE is used to pass back error information in onActivityResult -requestButton.setRequestBehavior(new RideRequestActivityBehavior(this, REQUEST_CODE)); -``` - -That's it! With this configuration, when a user clicks on the request button, an activity will be launched that contains a login view (on first launch) where the user can authorize your app. After authorization, this activity will contain the Ride Request View. If any unexpected errors occur that the SDK can't handle, the activity will finish with an error in the result Intent using either the key `RideRequestActivity.AUTHENTICATION_ERROR` or `RideRequestActivity.RIDE_REQUEST_ERROR` depending on where the error occurred. - -> **Note:** The environment ([sandbox](https://developer.uber.com/docs/rides/sandbox) or production) is considered by the Ride Request Widget. If you use the sample source code from above, your calls will be issued to the Sandbox. The widget will display a `sandbox` badge to indicate that. To change the mode, set environment to `Environment.PRODUCTION`. - ## Ride Request Button with ETA and price To further enhance the button with destination and price information, add a Session to it and call `loadRideInformation()` function. @@ -312,28 +332,6 @@ user1Manager.setAccessToken(accessToken); user2Manager.setAccessToken(accessToken2); ``` -### RideRequestView -The `RideRequestView` is like any other view you'd add to your app. Create a new instance in your XML layout or programmatically. You can optionally add custom `RideParameters` or a custom `AccessTokenSession`. When you're ready to show the Ride Request View, just call `load()`. - -```java -RideRequestView rideRequestView = new RideRequestView(context); - -//Optionally set Session, will use default session from UberSDK otherwise -//rideRequestView.setSession(session); - -rideRequestView.setRideParameters(rideParameters) -rideRequestView.setRideRequestViewCallback(new RideRequestViewErrorCallback() { - @Override - public void onErrorReceived(RideRequestViewError error) { - switch (error) { - // Handle errors - } - } -}); -layout.addView(rideRequestView); -rideRequestView.load(); -``` - ## Making an API Request The Android Uber SDK uses a dependency on the Java Uber SDK for API requests. After authentication is complete, create a `Session` to use the Uber API. @@ -410,11 +408,11 @@ Uber developers actively monitor the `uber-api` tag on StackOverflow. If you nee For full documentation about our API, visit our Developer Site. -## Migrating from a previous version -As the Uber Android SDK get closer to a 1.0 release, the API's will become more stable. In the meantime, be sure to check out the changelog to know what differs! - ## Contributing -We :heart: contributions. Found a bug or looking for a new feature? Open an issue and we'll respond as fast as we can. Or, better yet, implement it yourself and open a pull request! We ask that you include tests to show the bug was fixed or the feature works as expected. +We :heart: contributions. Found a bug or looking for a new feature? Open an issue and we'll +respond as fast as we can. Or, better yet, implement it yourself and open a pull request! We ask +that you open an issue to discuss feature development prior to undertaking the work and that you +include tests to show the bug was fixed or the feature works as expected. ## MIT Licensed diff --git a/core-android/src/main/java/com/uber/sdk/android/core/Deeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/Deeplink.java index c345eeb5..eca9c715 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/Deeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/Deeplink.java @@ -24,6 +24,10 @@ public interface Deeplink { + String DEEPLINK_SCHEME = "uber"; + String APP_LINK_URI = "https://m.uber.com/ul/"; + String MOBILE_WEB_URI = "https://m.uber.com/"; + /** * Actually send the deeplink. */ @@ -34,4 +38,8 @@ public interface Deeplink { */ boolean isSupported(); + enum Fallback { + APP_INSTALL, + MOBILE_WEB + } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 25b3f204..1bf1a42f 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -115,7 +115,7 @@ private Uri createSsoUri() { scopes = AuthUtils.mergeScopeStrings(scopes, AuthUtils.customScopeCollectionToString(requestedCustomScopes)); } - return new Uri.Builder().scheme(AppProtocol.DEEPLINK_SCHEME) + return new Uri.Builder().scheme(Deeplink.DEEPLINK_SCHEME) .authority(URI_HOST) .appendQueryParameter(URI_QUERY_CLIENT_ID, clientId) .appendQueryParameter(URI_QUERY_SCOPE, scopes) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 8e631a2c..b92bdae0 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -8,19 +8,16 @@ import android.content.pm.Signature; import android.os.Build; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Base64; - import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashSet; -import javax.annotation.Nullable; - public class AppProtocol { public static final String[] UBER_PACKAGE_NAMES = {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo", "com.ubercab.presidio.development"}; - public static final String DEEPLINK_SCHEME = "uber"; public static final String PLATFORM = "android"; private static final String UBER_RIDER_HASH = "411c40b31f6d01dac68d711df99b6eafeec8e73b"; @@ -45,6 +42,29 @@ public boolean validateMinimumVersion(Context context, PackageInfo packageInfo, return packageInfo.versionCode >= minimumVersion; } + public boolean isUberInstalled(@NonNull Context context) { + return getInstalledUberAppPackage(context) != null; + } + + @Nullable + PackageInfo getInstalledUberAppPackage(@NonNull Context context) { + PackageInfo packageInfo = null; + for (String installedPackage : AppProtocol.UBER_PACKAGE_NAMES) { + if (PackageManagers.isPackageAvailable(context, installedPackage)) { + packageInfo = PackageManagers.getPackageInfo(context, installedPackage); + break; + } + } + return packageInfo; + } + + /** + * @return true if the device supports App Links + */ + public boolean isAppLinkSupported() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } + /** * Validates the app signature required or returns true if in debug. */ diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java index a4e29137..e5a3c8c4 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java @@ -23,188 +23,16 @@ package com.uber.sdk.android.rides; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import com.uber.sdk.android.core.Deeplink; -import com.uber.sdk.android.core.UberSdk; -import com.uber.sdk.android.core.install.SignupDeeplink; -import com.uber.sdk.android.core.utils.AppProtocol; -import com.uber.sdk.android.core.utils.PackageManagers; -import com.uber.sdk.core.client.SessionConfiguration; - -import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; - /** - * A deeplink for requesting rides in the Uber application. - * - * @see Uber deeplink documentation + * @Deprecated use {@link RideRequestDeeplink} directly */ -public class RequestDeeplink implements Deeplink { - - private static final String USER_AGENT_DEEPLINK = String.format("rides-android-v%s-deeplink", - BuildConfig.VERSION_NAME); - - @NonNull - private final Uri uri; - - @NonNull - private final Context context; - - private RequestDeeplink(@NonNull Context context, @NonNull Uri uri) { - this.uri = uri; - this.context = context; - } - - /** - * Executes the deeplink to launch the Uber app. If the app is not installed redirects to the play store. - */ - public void execute() { - if (isSupported()) { - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - context.startActivity(intent); - } else { - Log.i(UberSdk.UBER_SDK_LOG_TAG, "Uber app not installed, redirecting to mobile sign up."); - final String clientId = uri.getQueryParameter(Builder.CLIENT_ID); - final String userAgent = uri.getQueryParameter(Builder.USER_AGENT); - - new SignupDeeplink(context, clientId, userAgent) - .execute(); - } - } - - @Override - public boolean isSupported() { - for (String packageName : AppProtocol.UBER_PACKAGE_NAMES) { - if(PackageManagers.isPackageAvailable(context, packageName)) { - return true; - } - } - return false; - } - - /** - * @return The {@link Uri} for the deeplink. - */ - @NonNull - public Uri getUri() { - return uri; - } - - /** - * 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 USER_AGENT = "user-agent"; - - private RideParameters rideParameters; - private SessionConfiguration sessionConfiguration; - private final Context context; - - /** - * @param context to execute the deeplink. - */ - public Builder(Context context) { - this.context = context; - } - - /** - * Sets the {@link RideParameters} for the deeplink. - * - * @return this instance of {@link Builder} - */ - public RequestDeeplink.Builder setRideParameters(@NonNull RideParameters rideParameters) { - this.rideParameters = rideParameters; - return this; - } - - /** - * Sets the client Id - * - * @return this instance of {@link Builder} - */ - public RequestDeeplink.Builder setSessionConfiguration(@NonNull SessionConfiguration loginConfiguration) { - sessionConfiguration = loginConfiguration; - return this; - } - - /** - * Builds an {@link RequestDeeplink} object. - * - * @return {@link RequestDeeplink} generated from parameters - */ - @NonNull - public RequestDeeplink build() { - checkNotNull(rideParameters, "Must supply ride parameters."); - checkNotNull(sessionConfiguration, "Must supply a Login Configuration"); - checkNotNull(sessionConfiguration.getClientId(), "Must supply client Id on Login Configuration"); - - - Uri.Builder builder = new Uri.Builder(); - builder.scheme(AppProtocol.DEEPLINK_SCHEME); - builder.appendQueryParameter(ACTION, SET_PICKUP); - builder.appendQueryParameter(CLIENT_ID, sessionConfiguration.getClientId()); - if (rideParameters.getProductId() != null) { - builder.appendQueryParameter(PRODUCT_ID, rideParameters.getProductId()); - } - if (rideParameters.getPickupLatitude() != null && rideParameters.getPickupLongitude() != null) { - addLocation(LocationType.PICKUP, Double.toString(rideParameters.getPickupLatitude()), - Double.toString(rideParameters.getPickupLongitude()), rideParameters.getPickupNickname(), - rideParameters.getPickupAddress(), builder); - } - if (rideParameters.isPickupMyLocation()) { - builder.appendQueryParameter(LocationType.PICKUP.getUriQueryKey(), MY_LOCATION); - } - if (rideParameters.getDropoffLatitude() != null && rideParameters.getDropoffLongitude() != null) { - addLocation(LocationType.DROPOFF, Double.toString(rideParameters.getDropoffLatitude()), - Double.toString(rideParameters.getDropoffLongitude()), rideParameters.getDropoffNickname(), - rideParameters.getDropoffAddress(), builder); - } - - String userAgent = rideParameters.getUserAgent(); - if (userAgent == null) { - userAgent = USER_AGENT_DEEPLINK; - } - builder.appendQueryParameter(USER_AGENT, userAgent); - - return new RequestDeeplink(context, builder.build()); - } - - 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 enum LocationType { - PICKUP, - DROPOFF; +@Deprecated +public class RequestDeeplink extends RideRequestDeeplink { - private String getUriQueryKey() { - return name().toLowerCase(); - } + RequestDeeplink(@NonNull Context context, @NonNull Uri uri) { + super(context, uri); } } diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java index 3bc8c5c5..72a1baa4 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java @@ -29,8 +29,9 @@ import com.uber.sdk.core.client.SessionConfiguration; /** - * The {@link RideRequestBehavior} to pass to the {@link RideRequestButton} to have it execute a {@link RequestDeeplink}. + * The {@link RideRequestBehavior} to pass to the {@link RideRequestButton} to have it execute a {@link RideRequestDeeplink}. */ +@Deprecated public class RequestDeeplinkBehavior implements RideRequestBehavior { final SessionConfiguration sessionConfiguration; @@ -51,14 +52,14 @@ public RequestDeeplinkBehavior(@NonNull SessionConfiguration configuration) { } /** - * Requests a ride using a {@link RequestDeeplink} that is constructed using the provided {@link RideParameters}. + * Requests a ride using a {@link RideRequestDeeplink} that is constructed using the provided {@link RideParameters}. * - * @param context {@link Context} to pass to launch the {@link RequestDeeplink} from - * @param params the {@link RideParameters} to use for building and executing the {@link RequestDeeplink} + * @param context {@link Context} to pass to launch the {@link RideRequestDeeplink} from + * @param params the {@link RideParameters} to use for building and executing the {@link RideRequestDeeplink} */ @Override public void requestRide(@NonNull Context context, @NonNull RideParameters params) { - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) .setSessionConfiguration(sessionConfiguration) .setRideParameters(params).build(); deeplink.execute(); diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java index caaaf182..cbb0d566 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java @@ -201,7 +201,7 @@ String getUserAgent() { } /** - * Sets the user agent, describing where this {@link RequestDeeplink} came from for analytics. + * Sets the user agent, describing where this {@link RideRequestDeeplink} came from for analytics. * @param userAgent to set */ void setUserAgent(@NonNull String userAgent) { diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java index 025bd84e..471b18b2 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java @@ -33,7 +33,11 @@ /** * The {@link RideRequestBehavior} to pass to the {@link RideRequestButton} to have it launch a {@link RideRequestActivity}. + + * @deprecated in favor of directly using mobile web directly. + * See https://developer.uber.com/docs/riders/ride-requests/tutorials/widget/migration-to-muber */ +@Deprecated public class RideRequestActivityBehavior implements RideRequestBehavior { @NonNull private final Activity activity; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestBehavior.java index 7d3fb026..8c297b83 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestBehavior.java @@ -26,7 +26,10 @@ /** * Interface to implement that describes actions for the {@link RideRequestButton}. + * + * @deprecated Use RideRequestDeeplink directly. */ +@Deprecated public interface RideRequestBehavior { /** diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java index b2423975..c7e554ab 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java @@ -37,6 +37,7 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.UberButton; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.UberStyle; @@ -75,6 +76,7 @@ public class RideRequestButton extends FrameLayout implements RideRequestButtonV private RideRequestButtonController controller; private Session session; private RideRequestButtonCallback callback; + private Deeplink.Fallback deeplinkFallback; public RideRequestButton(Context context) { this(context, null); @@ -98,7 +100,7 @@ public RideRequestButton(Context context, AttributeSet attrs, int defStyleAttr, init(context, attrs, defStyleAttr, uberStyle); } - private void init(Context context, AttributeSet attrs, int defStyleAttr, UberStyle uberStyle) { + private void init(final Context context, AttributeSet attrs, int defStyleAttr, UberStyle uberStyle) { @StyleRes int styleRes = STYLES[uberStyle.getValue()]; @@ -113,20 +115,25 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr, UberSty setTextAttributes(context, attrs, defStyleAttr, styleRes); showDefaultView(); + deeplinkFallback = Deeplink.Fallback.APP_INSTALL; setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { rideParameters.setUserAgent(USER_AGENT_BUTTON); - if (rideRequestBehavior == null) { - final SessionConfiguration config; - if (session != null) { - config = session.getAuthenticator().getSessionConfiguration(); - } else { - config = UberSdk.getDefaultSessionConfiguration(); - } - rideRequestBehavior = new RequestDeeplinkBehavior(config); + + final SessionConfiguration config; + if (session != null) { + config = session.getAuthenticator().getSessionConfiguration(); + } else { + config = UberSdk.getDefaultSessionConfiguration(); } - rideRequestBehavior.requestRide(getContext(), rideParameters); + + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setSessionConfiguration(config) + .setFallback(deeplinkFallback) + .setRideParameters(rideParameters) + .build(); + deeplink.execute(); } }); } @@ -142,12 +149,26 @@ public RideRequestButton setRideParameters(@NonNull RideParameters rideParameter return this; } + /** + * Sets the {@link Deeplink.Fallback} to be used when the Uber app isn't installed + * + * @return this instance of {@link RideRequestButton} + */ + public RideRequestButton setDeeplinkFallback(@NonNull Deeplink.Fallback fallback) { + this.deeplinkFallback = fallback; + return this; + } + /** * Sets how the request button should act for button actions. * * @param requestBehavior an object that implements {@link RideRequestBehavior} * @return this instance of {@link RideRequestButton} + * + * @deprecated Button will use deeplink by default use RideRequestButton to indicate fallback + * now instead. */ + @Deprecated public RideRequestButton setRequestBehavior(@NonNull RideRequestBehavior requestBehavior) { rideRequestBehavior = requestBehavior; return this; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java new file mode 100644 index 00000000..77a9d601 --- /dev/null +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2016 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.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.uber.sdk.android.core.Deeplink; +import com.uber.sdk.android.core.utils.AppProtocol; +import com.uber.sdk.core.client.SessionConfiguration; + +import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; + + +/** + * A deeplink for requesting rides in the Uber application. + * + * @see Uber deeplink documentation + */ +public class RideRequestDeeplink implements Deeplink { + + private static final String USER_AGENT_DEEPLINK = String.format("rides-android-v%s-deeplink", + BuildConfig.VERSION_NAME); + + @NonNull + private final Uri uri; + + @NonNull + private final Context context; + private final AppProtocol appProtocol; + + RideRequestDeeplink(@NonNull Context context, @NonNull Uri uri) { + this.uri = uri; + this.context = context; + appProtocol = new AppProtocol(); + } + + /** + * Executes the deeplink to launch the Uber app. If the app is not installed redirects to the play store. + */ + public void execute() { + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + context.startActivity(intent); + } + + @Override + public boolean isSupported() { + return appProtocol.isUberInstalled(context); + } + + /** + * @return The {@link Uri} for the deeplink. + */ + @NonNull + public Uri getUri() { + return uri; + } + + /** + * Builder for {@link RideRequestDeeplink} objects. + */ + public static class Builder { + + public static final String AUTHORITY = "riderequest"; + 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 USER_AGENT = "user-agent"; + + private RideParameters rideParameters; + private SessionConfiguration sessionConfiguration; + private Fallback fallback = Fallback.APP_INSTALL; + private final Context context; + private AppProtocol appProtocol; + + /** + * @param context to execute the deeplink. + */ + public Builder(Context context) { + this(context, new AppProtocol()); + } + + Builder(Context context, AppProtocol appProtocol) { + this.context = context; + this.appProtocol = appProtocol; + } + + /** + * Sets the {@link RideParameters} for the deeplink. + * + * @return this instance of {@link Builder} + */ + public RideRequestDeeplink.Builder setRideParameters(@NonNull RideParameters rideParameters) { + this.rideParameters = rideParameters; + return this; + } + + /** + * Sets the client Id + * + * @return this instance of {@link Builder} + */ + public RideRequestDeeplink.Builder setSessionConfiguration(@NonNull SessionConfiguration + sessionConfiguration) { + this.sessionConfiguration = sessionConfiguration; + return this; + } + + /** + * Sets the fallback to use when the Uber app isn't installed. + * + * @return this instance of {@link Builder} + */ + public RideRequestDeeplink.Builder setFallback(@NonNull Fallback fallback) { + this.fallback = fallback; + return this; + } + + /** + * Builds an {@link RideRequestDeeplink} object. + * + * @return {@link RideRequestDeeplink} generated from parameters + */ + @NonNull + public RideRequestDeeplink build() { + checkNotNull(rideParameters, "Must supply ride parameters."); + checkNotNull(sessionConfiguration, "Must supply a Session Configuration"); + checkNotNull(sessionConfiguration.getClientId(), "Must supply client Id on Login Configuration"); + + final Uri.Builder builder = getUriBuilder(context, fallback); + + builder.appendQueryParameter(ACTION, SET_PICKUP); + builder.appendQueryParameter(CLIENT_ID, sessionConfiguration.getClientId()); + if (rideParameters.getProductId() != null) { + builder.appendQueryParameter(PRODUCT_ID, rideParameters.getProductId()); + } + if (rideParameters.getPickupLatitude() != null && rideParameters.getPickupLongitude() != null) { + addLocation(LocationType.PICKUP, Double.toString(rideParameters.getPickupLatitude()), + Double.toString(rideParameters.getPickupLongitude()), rideParameters.getPickupNickname(), + rideParameters.getPickupAddress(), builder); + } + if (rideParameters.isPickupMyLocation()) { + builder.appendQueryParameter(LocationType.PICKUP.getUriQueryKey(), MY_LOCATION); + } + if (rideParameters.getDropoffLatitude() != null && rideParameters.getDropoffLongitude() != null) { + addLocation(LocationType.DROPOFF, Double.toString(rideParameters.getDropoffLatitude()), + Double.toString(rideParameters.getDropoffLongitude()), rideParameters.getDropoffNickname(), + rideParameters.getDropoffAddress(), builder); + } + + String userAgent = rideParameters.getUserAgent(); + if (userAgent == null) { + userAgent = USER_AGENT_DEEPLINK; + } + builder.appendQueryParameter(USER_AGENT, userAgent); + + return new RideRequestDeeplink(context, builder.build()); + } + + 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); + } + } + + Uri.Builder getUriBuilder(@NonNull Context context, @NonNull Deeplink.Fallback + fallback) { + final Uri.Builder builder; + if (appProtocol.isUberInstalled(context)) { + if (appProtocol.isAppLinkSupported()) { + builder = Uri.parse(Deeplink.APP_LINK_URI).buildUpon(); + } else { + builder = new Uri.Builder() + .scheme(Deeplink.DEEPLINK_SCHEME); + } + } else { + if (fallback == Deeplink.Fallback.MOBILE_WEB) { + builder = Uri.parse(Deeplink.MOBILE_WEB_URI).buildUpon(); + } else { + builder = Uri.parse(Deeplink.APP_LINK_URI).buildUpon(); + } + } + return builder; + } + } + + private enum LocationType { + PICKUP, + DROPOFF; + + private String getUriQueryKey() { + return name().toLowerCase(); + } + } +} diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java index 8945ead6..b87fbab3 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java @@ -52,7 +52,11 @@ /** * The Uber Ride Request View: an embeddable view that provides the end-to-end Uber experience. * The primary way to interact with this view after construction is to call the load() function. + * + * @deprecated in favor of directly using mobile web directly. + * See https://developer.uber.com/docs/riders/ride-requests/tutorials/widget/migration-to-muber */ +@Deprecated public class RideRequestView extends LinearLayout { private static final String USER_AGENT_RIDE_VIEW = String.format("rides-android-v%s-ride_request_view", @@ -175,7 +179,7 @@ static String buildUrlFromRideParameters(@NonNull Context context, rideParameters.setUserAgent(USER_AGENT_RIDE_VIEW); } - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) .setSessionConfiguration(loginConfiguration) .setRideParameters(rideParameters).build(); Uri uri = deeplink.getUri(); diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java similarity index 55% rename from rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java rename to rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java index ea4aa78a..1fe89af2 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java @@ -26,10 +26,17 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; +import android.net.Uri; +import com.uber.sdk.android.core.Deeplink; +import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.client.SessionConfiguration; +import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.res.builder.RobolectricPackageManager; @@ -38,13 +45,17 @@ import java.io.IOException; import static com.uber.sdk.android.rides.TestUtils.readUriResourceWithUserAgentParam; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; /** - * Tests {@link RequestDeeplink} + * Tests {@link RideRequestDeeplink} */ -public class RequestDeeplinkTest extends RobolectricTestBase { +public class RideRequestDeeplinkTest extends RobolectricTestBase { private static final String UBER_PACKAGE_NAME = "com.ubercab"; private static final String CLIENT_ID = "clientId"; @@ -60,7 +71,17 @@ public class RequestDeeplinkTest extends RobolectricTestBase { private static final String USER_AGENT_DEEPLINK = String .format("rides-android-v%s-deeplink", BuildConfig.VERSION_NAME); - private Context context; + @Mock + Context context; + + @Mock AppProtocol appProtocol; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(appProtocol.isUberInstalled(eq(context))).thenReturn(true); + when(appProtocol.isAppLinkSupported()).thenReturn(false); + } @Test public void onBuildDeeplink_whenClientIdAndDefaultRideParamsProvided_shouldHaveDefaults() throws IOException { @@ -68,7 +89,7 @@ public void onBuildDeeplink_whenClientIdAndDefaultRideParamsProvided_shouldHaveD USER_AGENT_DEEPLINK); RideParameters rideParameters = new RideParameters.Builder().build(); - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -86,7 +107,7 @@ public void onBuildDeeplink_whenFullRideParamsProvided_shouldCompleteUri() throw .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR) .setProductId(PRODUCT_ID) .build(); - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -103,7 +124,7 @@ public void onBuildDeeplink_whenPickupAndClientIdProvided_shouldNotHaveDropoffOr RideParameters rideParameters = new RideParameters.Builder() .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR) .build(); - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -121,7 +142,7 @@ public void onBuildDeeplink_whenDropoffClientIdAndProductIdProvided_shouldHaveDe .setProductId(PRODUCT_ID) .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR) .build(); - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -140,7 +161,7 @@ public void onBuildDeeplink_whenNoNicknameOrAddressProvided_shouldNotHaveNicknam .setPickupLocation(PICKUP_LAT, PICKUP_LONG, null, null) .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, null, null) .build(); - RequestDeeplink deeplink = new RequestDeeplink.Builder(context) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -150,53 +171,113 @@ public void onBuildDeeplink_whenNoNicknameOrAddressProvided_shouldNotHaveNicknam @Test(expected = NullPointerException.class) public void onBuildDeeplink_whenNoRideParams_shouldNotBuild() { - new RequestDeeplink.Builder(context).build(); + new RideRequestDeeplink.Builder(context, appProtocol).build(); } @Test - public void execute_whenNoUberApp_shouldPointToMobileSite() throws IOException { - String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/no_app_installed", + public void getUri_whenUberAppInstalledAndAppLinkSupported_shouldUseAppLink() throws IOException { + String expectedUri = readUriResourceWithUserAgentParam + ("src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided", USER_AGENT_DEEPLINK); - Activity activity = Robolectric.setupActivity(Activity.class); - ShadowActivity shadowActivity = shadowOf(activity); + when(appProtocol.isUberInstalled(eq(context))).thenReturn(true); + when(appProtocol.isAppLinkSupported()).thenReturn(true); RideParameters rideParameters = new RideParameters.Builder().build(); - RequestDeeplink requestDeeplink = new RequestDeeplink.Builder(activity) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); - requestDeeplink.execute(); - Intent startedIntent = shadowActivity.getNextStartedActivity(); - assertEquals(expectedUri, startedIntent.getData().toString()); + assertThat(rideRequestDeeplink.getUri().toString()).isEqualTo(expectedUri); } @Test - public void execute_whenUberAppInsalled_shouldPointToUberApp() throws IOException { - String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/just_client_provided", - USER_AGENT_DEEPLINK); + public void getUri_whenUberAppInstalledAndAppLinkNotSupported_shouldUseNativeLink() 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); + when(appProtocol.isUberInstalled(eq(context))).thenReturn(true); + when(appProtocol.isAppLinkSupported()).thenReturn(false); - RobolectricPackageManager packageManager = RuntimeEnvironment.getRobolectricPackageManager(); + RideParameters rideParameters = new RideParameters.Builder().build(); + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + .setRideParameters(rideParameters) + .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) + .build(); - PackageInfo uberPackage = new PackageInfo(); - uberPackage.packageName = UBER_PACKAGE_NAME; - packageManager.addPackage(uberPackage); + assertThat(rideRequestDeeplink.getUri().toString()).isEqualTo(expectedUri); + } + @Test + public void getUri_whenUberAppNotInstalledAndFallbackMobileWeb_shouldUseMobileWeb() throws IOException { + String expectedUri = readUriResourceWithUserAgentParam + ("src/test/resources/deeplinkuris/mobile_web_just_client_provided", + USER_AGENT_DEEPLINK); + + when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); + + RideParameters rideParameters = new RideParameters.Builder().build(); + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + .setRideParameters(rideParameters) + .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) + .setFallback(Deeplink.Fallback.MOBILE_WEB) + .build(); + + assertThat(rideRequestDeeplink.getUri().toString()).isEqualTo(expectedUri); + } + + @Test + public void getUri_whenUberAppNotInstalledAndFallbackAppInstall_shouldUseAppInstall() throws IOException { + String expectedUri = readUriResourceWithUserAgentParam + ("src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided", + USER_AGENT_DEEPLINK); + + when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); + + RideParameters rideParameters = new RideParameters.Builder().build(); + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + .setRideParameters(rideParameters) + .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) + .setFallback(Deeplink.Fallback.APP_INSTALL) + .build(); + + assertThat(rideRequestDeeplink.getUri().toString()).isEqualTo(expectedUri); + } + + @Test + public void getUri_whenUberAppNotInstalledAndFallbackNotSet_shouldUseAppInstall() throws IOException { + String expectedUri = readUriResourceWithUserAgentParam + ("src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided", + USER_AGENT_DEEPLINK); + + when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); + + RideParameters rideParameters = new RideParameters.Builder().build(); + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + .setRideParameters(rideParameters) + .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) + .build(); + + assertThat(rideRequestDeeplink.getUri().toString()).isEqualTo(expectedUri); + } + + @Test + public void getUri_callsStartActivity() { RideParameters rideParameters = new RideParameters.Builder().build(); - RequestDeeplink requestDeeplink = new RequestDeeplink.Builder(activity) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); - requestDeeplink.execute(); - Intent startedIntent = shadowActivity.getNextStartedActivity(); - assertEquals(expectedUri, startedIntent.getData().toString()); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); + rideRequestDeeplink.execute(); + verify(context).startActivity(argumentCaptor.capture()); + Intent intent = argumentCaptor.getValue(); + assertThat(intent.getData()).isEqualTo(rideRequestDeeplink.getUri()); } } diff --git a/rides-android/src/test/resources/deeplinkuris/mobile_web_just_client_provided b/rides-android/src/test/resources/deeplinkuris/mobile_web_just_client_provided new file mode 100644 index 00000000..3b675a52 --- /dev/null +++ b/rides-android/src/test/resources/deeplinkuris/mobile_web_just_client_provided @@ -0,0 +1 @@ +https://m.uber.com/?action=setPickup&client_id=clientId&pickup=my_location diff --git a/rides-android/src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided b/rides-android/src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided new file mode 100644 index 00000000..c8534a07 --- /dev/null +++ b/rides-android/src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided @@ -0,0 +1 @@ +https://m.uber.com/ul/?action=setPickup&client_id=clientId&pickup=my_location 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 index 5c9bdc69..ba142dd7 100644 --- 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 @@ -107,12 +107,7 @@ protected void onCreate(Bundle savedInstanceState) { .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR) .build(); - // This button demonstrates launching the RideRequestActivity (customized button behavior). - // You can optionally setRideParameters for pre-filled pickup and dropoff locations. RideRequestButton uberButtonWhite = (RideRequestButton) findViewById(R.id.uber_button_white); - RideRequestActivityBehavior rideRequestActivityBehavior = new RideRequestActivityBehavior(this, - WIDGET_REQUEST_CODE, configuration); - uberButtonWhite.setRequestBehavior(rideRequestActivityBehavior); uberButtonWhite.setRideParameters(rideParametersForProduct); uberButtonWhite.setSession(session); uberButtonWhite.loadRideInformation(); @@ -134,24 +129,6 @@ public void onError(Throwable throwable) { Toast.makeText(this, "Connection error", Toast.LENGTH_LONG).show(); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == WIDGET_REQUEST_CODE && resultCode == Activity.RESULT_CANCELED && data != null) { - if (data.getSerializableExtra(RideRequestActivity.AUTHENTICATION_ERROR) != null) { - AuthenticationError error = (AuthenticationError) data.getSerializableExtra(RideRequestActivity - .AUTHENTICATION_ERROR); - Toast.makeText(SampleActivity.this, "Auth error " + error.name(), Toast.LENGTH_SHORT).show(); - Log.d(ERROR_LOG_TAG, "Error occurred during authentication: " + error.toString - ().toLowerCase()); - } else if (data.getSerializableExtra(RideRequestActivity.RIDE_REQUEST_ERROR) != null) { - RideRequestViewError error = (RideRequestViewError) data.getSerializableExtra(RideRequestActivity - .RIDE_REQUEST_ERROR); - Toast.makeText(SampleActivity.this, "RideRequest error " + error.name(), Toast.LENGTH_SHORT).show(); - Log.d(ERROR_LOG_TAG, "Error occurred in the Ride Request Widget: " + error.toString().toLowerCase()); - } - } - } - @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. From e418ae24a389eca5d331d2ac3f7e33fedd593162 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 8 Feb 2018 23:32:31 -0800 Subject: [PATCH 052/165] Adding chrometab support for RideRequest deeplink --- .../sdk/android/core/auth/LoginActivity.java | 1 + .../{auth => utils}/CustomTabsHelper.java | 22 +++++++++---------- .../android/rides/RideRequestDeeplink.java | 6 +++-- .../android/rides/samples/SampleActivity.java | 2 ++ .../src/main/res/layout/activity_sample.xml | 2 +- .../src/main/res/values/strings.xml | 4 ++-- 6 files changed, 21 insertions(+), 16 deletions(-) rename core-android/src/main/java/com/uber/sdk/android/core/{auth => utils}/CustomTabsHelper.java (92%) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index b634bc83..2830a37d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -38,6 +38,7 @@ import android.webkit.WebViewClient; import com.uber.sdk.android.core.R; +import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; /** diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java similarity index 92% rename from core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java rename to core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java index 2b287699..dd43a267 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.uber.sdk.android.core.auth; +package com.uber.sdk.android.core.utils; import android.app.Activity; import android.content.ComponentName; @@ -62,11 +62,11 @@ private CustomTabsHelper() {} * @param fallback a CustomTabFallback to be used if Custom Tabs is not available. */ public static void openCustomTab( - final Activity activity, + final Context context, final CustomTabsIntent customTabsIntent, final Uri uri, CustomTabFallback fallback) { - final String packageName = getPackageNameToUse(activity); + final String packageName = getPackageNameToUse(context); if (packageName != null) { final CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { @@ -76,14 +76,14 @@ public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabs customTabsIntent.intent.setPackage(packageName); customTabsIntent.intent.setData(uri); - customTabsIntent.launchUrl(activity, uri); + customTabsIntent.launchUrl(context, uri); } @Override public void onServiceDisconnected(ComponentName name) {} }; - CustomTabsClient.bindCustomTabsService(activity, packageName, connection); + CustomTabsClient.bindCustomTabsService(context, packageName, connection); } else if (fallback != null) { - fallback.openUri(activity, uri); + fallback.openUri(context, uri); } else { Log.e(UberSdk.UBER_SDK_LOG_TAG, "Use of openCustomTab without Customtab support or a fallback set"); @@ -184,12 +184,12 @@ public static String[] getPackages() { /** * Fallback that uses browser */ - static class BrowserFallback implements CustomTabFallback { + public static class BrowserFallback implements CustomTabFallback { @Override - public void openUri(Activity activity, Uri uri) { + public void openUri(@NonNull Context context, Uri uri) { Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - activity.startActivity(intent); + context.startActivity(intent); } } @@ -199,9 +199,9 @@ public void openUri(Activity activity, Uri uri) { interface CustomTabFallback { /** * - * @param activity The Activity that wants to open the Uri. + * @param context The Context that wants to open the Uri. * @param uri The uri to be opened by the fallback. */ - void openUri(Activity activity, Uri uri); + void openUri(@NonNull Context context, Uri uri); } } \ No newline at end of file diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java index 77a9d601..66a66671 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java @@ -27,9 +27,11 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.customtabs.CustomTabsIntent; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.utils.AppProtocol; +import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; @@ -62,8 +64,8 @@ public class RideRequestDeeplink implements Deeplink { * Executes the deeplink to launch the Uber app. If the app is not installed redirects to the play store. */ public void execute() { - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - context.startActivity(intent); + final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); + CustomTabsHelper.openCustomTab(context, intent, uri, new CustomTabsHelper.BrowserFallback()); } @Override 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 index ba142dd7..bfaebc9a 100644 --- 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 @@ -34,6 +34,7 @@ import android.view.MenuItem; import android.widget.Toast; +import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.rides.RideParameters; @@ -110,6 +111,7 @@ protected void onCreate(Bundle savedInstanceState) { RideRequestButton uberButtonWhite = (RideRequestButton) findViewById(R.id.uber_button_white); uberButtonWhite.setRideParameters(rideParametersForProduct); uberButtonWhite.setSession(session); + uberButtonWhite.setDeeplinkFallback(Deeplink.Fallback.MOBILE_WEB); uberButtonWhite.loadRideInformation(); } 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 index d939c486..b10177c9 100644 --- a/samples/request-button-sample/src/main/res/layout/activity_sample.xml +++ b/samples/request-button-sample/src/main/res/layout/activity_sample.xml @@ -60,7 +60,7 @@ diff --git a/samples/request-button-sample/src/main/res/values/strings.xml b/samples/request-button-sample/src/main/res/values/strings.xml index 97201848..2c137a8f 100644 --- a/samples/request-button-sample/src/main/res/values/strings.xml +++ b/samples/request-button-sample/src/main/res/values/strings.xml @@ -28,6 +28,6 @@ Copy Access Token to Clipboard Clear Access Token Refresh Estimates - Deep Link into Uber App - Ride Request Widget + Deeplink with app install fallback + Deeplink with mobile web fallback From d9597ef27ee11e4915cb5c336d3283d4e5bac136 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 8 Feb 2018 23:42:27 -0800 Subject: [PATCH 053/165] Adding Fallback section for button to readme --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae54c4b2..27a06550 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ You can also add your button through XML: To use the `uber` XML namespace, be sure to add `xmlns:uber="http://schemas.android.com/apk/res-auto"` to your root view element. -### Color Style +### Customization The default color has a black background with white text: ```xml @@ -128,6 +128,14 @@ For a button with a white background and black text: uber:ub__style="white"/> ``` +To specify the mobile web deeplink fallback over app installation when using the +`RideRequestButton`: + +```java +rideRequestButton.setDeeplinkFallback(Deeplink.Fallback.MOBILE_WEB); +``` + + With all the necessary parameters set, pressing the button will seamlessly prompt a ride request confirmation screen. ## Ride Request Button with ETA and price From 96f6ab09982ada18b5915c81d06e2ef167f54879 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 9 Feb 2018 08:06:38 -0800 Subject: [PATCH 054/165] Fixing test failure --- .../sdk/android/core/auth/LoginActivity.java | 3 +- .../android/core/utils/CustomTabsHelper.java | 14 ++--- .../sdk/android/rides/RequestDeeplink.java | 11 +++- .../android/rides/RideRequestDeeplink.java | 43 +++++++++---- .../rides/RideRequestDeeplinkTest.java | 61 ++++++++++++------- 5 files changed, 90 insertions(+), 42 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 2830a37d..8b22bc02 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -211,7 +211,8 @@ protected void loadWebview(String url, String redirectUri) { protected void loadChrometab(String url) { final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); - CustomTabsHelper.openCustomTab(this, intent, Uri.parse(url), new CustomTabsHelper + CustomTabsHelper customTabsHelper = new CustomTabsHelper(); + customTabsHelper.openCustomTab(this, intent, Uri.parse(url), new CustomTabsHelper .BrowserFallback()); } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java index dd43a267..471ba75c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java @@ -51,17 +51,17 @@ public class CustomTabsHelper { private static String packageNameToUse; - private CustomTabsHelper() {} + public CustomTabsHelper() {} /** * Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView. * - * @param activity The host activity. + * @param context The host context. * @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available. * @param uri the Uri to be opened. * @param fallback a CustomTabFallback to be used if Custom Tabs is not available. */ - public static void openCustomTab( + public void openCustomTab( final Context context, final CustomTabsIntent customTabsIntent, final Uri uri, @@ -101,7 +101,7 @@ public void onServiceDisconnected(ComponentName name) {} * @return The package name recommended to use for connecting to custom tabs related components. */ @Nullable - public static String getPackageNameToUse(Context context) { + public String getPackageNameToUse(Context context) { if (packageNameToUse != null) return packageNameToUse; PackageManager pm = context.getPackageManager(); @@ -152,7 +152,7 @@ public static String getPackageNameToUse(Context context) { * @param intent The intent to check with. * @return Whether there is a specialized handler for the given intent. */ - private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) { + private boolean hasSpecializedHandlerIntents(Context context, Intent intent) { try { PackageManager pm = context.getPackageManager(); List handlers = pm.queryIntentActivities( @@ -177,7 +177,7 @@ private static boolean hasSpecializedHandlerIntents(Context context, Intent inte /** * @return All possible chrome package names that provide custom tabs feature. */ - public static String[] getPackages() { + public String[] getPackages() { return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE}; } @@ -196,7 +196,7 @@ public void openUri(@NonNull Context context, Uri uri) { /** * To be used as a fallback to open the Uri when Custom Tabs is not available. */ - interface CustomTabFallback { + public interface CustomTabFallback { /** * * @param context The Context that wants to open the Uri. diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java index e5a3c8c4..03da7bd1 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java @@ -26,13 +26,20 @@ import android.net.Uri; import android.support.annotation.NonNull; +import com.uber.sdk.android.core.utils.AppProtocol; +import com.uber.sdk.android.core.utils.CustomTabsHelper; + /** * @Deprecated use {@link RideRequestDeeplink} directly */ @Deprecated public class RequestDeeplink extends RideRequestDeeplink { - RequestDeeplink(@NonNull Context context, @NonNull Uri uri) { - super(context, uri); + RequestDeeplink( + @NonNull Context context, + @NonNull Uri uri, + @NonNull AppProtocol appProtocol, + @NonNull CustomTabsHelper customTabsHelper) { + super(context, uri, appProtocol, customTabsHelper); } } diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java index 66a66671..af720c80 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java @@ -23,10 +23,10 @@ package com.uber.sdk.android.rides; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.customtabs.CustomTabsIntent; import com.uber.sdk.android.core.Deeplink; @@ -49,15 +49,21 @@ public class RideRequestDeeplink implements Deeplink { @NonNull private final Uri uri; - @NonNull private final Context context; + @NonNull private final AppProtocol appProtocol; + @NonNull + private final CustomTabsHelper customTabsHelper; - RideRequestDeeplink(@NonNull Context context, @NonNull Uri uri) { + RideRequestDeeplink(@NonNull Context context, + @NonNull Uri uri, + @NonNull AppProtocol appProtocol, + @NonNull CustomTabsHelper customTabsHelper) { this.uri = uri; this.context = context; - appProtocol = new AppProtocol(); + this.appProtocol = appProtocol; + this.customTabsHelper = customTabsHelper; } /** @@ -65,7 +71,7 @@ public class RideRequestDeeplink implements Deeplink { */ public void execute() { final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); - CustomTabsHelper.openCustomTab(context, intent, uri, new CustomTabsHelper.BrowserFallback()); + customTabsHelper.openCustomTab(context, intent, uri, new CustomTabsHelper.BrowserFallback()); } @Override @@ -103,17 +109,13 @@ public static class Builder { private Fallback fallback = Fallback.APP_INSTALL; private final Context context; private AppProtocol appProtocol; + private CustomTabsHelper customTabsHelper; /** * @param context to execute the deeplink. */ public Builder(Context context) { - this(context, new AppProtocol()); - } - - Builder(Context context, AppProtocol appProtocol) { this.context = context; - this.appProtocol = appProtocol; } /** @@ -147,6 +149,18 @@ public RideRequestDeeplink.Builder setFallback(@NonNull Fallback fallback) { return this; } + @VisibleForTesting + RideRequestDeeplink.Builder setCustomTabsHelper(@NonNull CustomTabsHelper customTabsHelper) { + this.customTabsHelper = customTabsHelper; + return this; + } + + @VisibleForTesting + RideRequestDeeplink.Builder setAppProtocol(@NonNull AppProtocol appProtocol){ + this.appProtocol = appProtocol; + return this; + } + /** * Builds an {@link RideRequestDeeplink} object. * @@ -158,6 +172,13 @@ public RideRequestDeeplink build() { checkNotNull(sessionConfiguration, "Must supply a Session Configuration"); checkNotNull(sessionConfiguration.getClientId(), "Must supply client Id on Login Configuration"); + if (appProtocol == null) { + appProtocol = new AppProtocol(); + } + if (customTabsHelper == null) { + customTabsHelper = new CustomTabsHelper(); + } + final Uri.Builder builder = getUriBuilder(context, fallback); builder.appendQueryParameter(ACTION, SET_PICKUP); @@ -185,7 +206,7 @@ public RideRequestDeeplink build() { } builder.appendQueryParameter(USER_AGENT, userAgent); - return new RideRequestDeeplink(context, builder.build()); + return new RideRequestDeeplink(context, builder.build(), appProtocol, customTabsHelper); } private void addLocation( diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java index 1fe89af2..4c944b3a 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java @@ -26,10 +26,15 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.support.customtabs.CustomTabsIntent; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.utils.AppProtocol; +import com.uber.sdk.android.core.utils.CustomTabsHelper; +import com.uber.sdk.android.core.utils.PackageManagers; import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; @@ -47,6 +52,7 @@ import static com.uber.sdk.android.rides.TestUtils.readUriResourceWithUserAgentParam; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -71,10 +77,9 @@ public class RideRequestDeeplinkTest extends RobolectricTestBase { private static final String USER_AGENT_DEEPLINK = String .format("rides-android-v%s-deeplink", BuildConfig.VERSION_NAME); - @Mock - Context context; - + @Mock Context context; @Mock AppProtocol appProtocol; + @Mock CustomTabsHelper customTabsHelper; @Before public void setup() { @@ -89,7 +94,8 @@ public void onBuildDeeplink_whenClientIdAndDefaultRideParamsProvided_shouldHaveD USER_AGENT_DEEPLINK); RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -107,7 +113,8 @@ public void onBuildDeeplink_whenFullRideParamsProvided_shouldCompleteUri() throw .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR) .setProductId(PRODUCT_ID) .build(); - RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -124,7 +131,8 @@ public void onBuildDeeplink_whenPickupAndClientIdProvided_shouldNotHaveDropoffOr RideParameters rideParameters = new RideParameters.Builder() .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR) .build(); - RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -142,7 +150,8 @@ public void onBuildDeeplink_whenDropoffClientIdAndProductIdProvided_shouldHaveDe .setProductId(PRODUCT_ID) .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR) .build(); - RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -161,7 +170,8 @@ public void onBuildDeeplink_whenNoNicknameOrAddressProvided_shouldNotHaveNicknam .setPickupLocation(PICKUP_LAT, PICKUP_LONG, null, null) .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, null, null) .build(); - RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink deeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -171,7 +181,7 @@ public void onBuildDeeplink_whenNoNicknameOrAddressProvided_shouldNotHaveNicknam @Test(expected = NullPointerException.class) public void onBuildDeeplink_whenNoRideParams_shouldNotBuild() { - new RideRequestDeeplink.Builder(context, appProtocol).build(); + new RideRequestDeeplink.Builder(context).build(); } @Test @@ -185,7 +195,8 @@ public void getUri_whenUberAppInstalledAndAppLinkSupported_shouldUseAppLink() th RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -203,7 +214,8 @@ public void getUri_whenUberAppInstalledAndAppLinkNotSupported_shouldUseNativeLin when(appProtocol.isAppLinkSupported()).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -220,7 +232,8 @@ public void getUri_whenUberAppNotInstalledAndFallbackMobileWeb_shouldUseMobileWe when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .setFallback(Deeplink.Fallback.MOBILE_WEB) @@ -238,7 +251,8 @@ public void getUri_whenUberAppNotInstalledAndFallbackAppInstall_shouldUseAppInst when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .setFallback(Deeplink.Fallback.APP_INSTALL) @@ -256,7 +270,8 @@ public void getUri_whenUberAppNotInstalledAndFallbackNotSet_shouldUseAppInstall( when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); @@ -265,19 +280,23 @@ public void getUri_whenUberAppNotInstalledAndFallbackNotSet_shouldUseAppInstall( } @Test - public void getUri_callsStartActivity() { + public void getUri_withCustomTab_callsCustomTabHelper() { RideParameters rideParameters = new RideParameters.Builder().build(); - RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context, appProtocol) + + RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) + .setAppProtocol(appProtocol) + .setCustomTabsHelper(customTabsHelper) .setRideParameters(rideParameters) .setSessionConfiguration(new SessionConfiguration.Builder().setClientId("clientId").build()) .build(); - - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); rideRequestDeeplink.execute(); - verify(context).startActivity(argumentCaptor.capture()); - Intent intent = argumentCaptor.getValue(); - assertThat(intent.getData()).isEqualTo(rideRequestDeeplink.getUri()); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Uri.class); + verify(customTabsHelper).openCustomTab(eq(context), any(CustomTabsIntent.class), + argumentCaptor.capture(), any(CustomTabsHelper.BrowserFallback.class)); + + Uri uri = argumentCaptor.getValue(); + assertThat(uri).isEqualTo(rideRequestDeeplink.getUri()); } } From 8c3988d11ac199ebb81addbdaf08b28e91053a8c Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 9 Feb 2018 17:08:17 -0800 Subject: [PATCH 055/165] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27a06550..c41e6d90 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Official Android SDK to support: - Ride Request Button - - RIde Request Deeplinks + - Ride Request Deeplinks - Uber Authentication - REST APIs From 48631444a360881228f344eb04a9c56a2df83339 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Sat, 10 Feb 2018 11:56:06 -0800 Subject: [PATCH 056/165] Fixing changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d1c905f..5542ebf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ v0.8.1 - TBD ------------ +### Added +- [Issue #111](https://github.com/uber/rides-android-sdk/issues/111) Add Uber Mobile Web support + over deprecated Ride Request Widget + v0.8.0 - 02/09/2018 ------------ @@ -9,8 +13,6 @@ v0.8.0 - 02/09/2018 ### Added - [Issue #22](https://github.com/uber/rides-android-sdk/issues/22) Customtab support - - [Issue #111](https://github.com/uber/rides-android-sdk/issues/111) Add Uber Mobile Web support - over deprecated Ride Request Widget v0.7.0 - 11/17/2017 From ce2af6036a2199378338d3831e2668837a305f62 Mon Sep 17 00:00:00 2001 From: Huang Huaxun Date: Wed, 3 Jan 2018 13:05:16 +0800 Subject: [PATCH 057/165] add deprecated member "onReceivedError" to solve compatibility issue when API level < 23 --- .../sdk/android/core/auth/LoginActivity.java | 21 ++++++++++++++++++ .../sdk/android/rides/RideRequestView.java | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 8b22bc02..4a7f40bb 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -22,10 +22,12 @@ package com.uber.sdk.android.core.auth; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; @@ -273,8 +275,27 @@ public OAuthWebViewClient(@NonNull String redirectUri) { this.redirectUri = redirectUri; } + /** + * add deprecated member "onReceivedError" to solve compatibility issue when API level < 23 + * @param view + * @param errorCode + * @param description + * @param failingUrl + */ + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + if(Build.VERSION.SDK_INT Date: Tue, 13 Feb 2018 13:23:38 -0800 Subject: [PATCH 058/165] Cleanup --- CHANGELOG.md | 3 +++ .../java/com/uber/sdk/android/core/auth/LoginActivity.java | 2 +- .../java/com/uber/sdk/android/rides/RideRequestView.java | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5542ebf0..75693ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ v0.8.1 - TBD - [Issue #111](https://github.com/uber/rides-android-sdk/issues/111) Add Uber Mobile Web support over deprecated Ride Request Widget +### Fixed + - [Issue #105](https://github.com/uber/rides-android-sdk/issues/105) onReceivedError and onReceivedHttpError does not work on API level < 23 + v0.8.0 - 02/09/2018 ------------ diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 4a7f40bb..9ef048d9 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -284,7 +284,7 @@ public OAuthWebViewClient(@NonNull String redirectUri) { */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - if(Build.VERSION.SDK_INT Date: Tue, 13 Feb 2018 13:28:28 -0800 Subject: [PATCH 059/165] Removing dup code --- .../com/uber/sdk/android/core/auth/LoginActivity.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 9ef048d9..7f33ef6e 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -295,12 +295,12 @@ public void onReceivedError(WebView view, WebResourceRequest request, WebResourc receivedError(); } - private void receivedError(){ - onError(AuthenticationError.CONNECTIVITY_ISSUE); - } - @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + receivedError(); + } + + private void receivedError(){ onError(AuthenticationError.CONNECTIVITY_ISSUE); } } From d8728bb3f925dbce5aae562021ebf829cf88e3b8 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 13 Feb 2018 14:03:41 -0800 Subject: [PATCH 060/165] [Gradle Release Plugin] - pre tag commit: 'v0.9.0'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75693ee8..290219e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.8.1 - TBD +v0.9.0 - 02/13/2018 ------------ ### Added diff --git a/gradle.properties b/gradle.properties index eefaa1c9..9ccb338c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Fri, 09 Feb 2018 08:23:14 -0800 +#Tue, 13 Feb 2018 14:01:15 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.8.1-SNAPSHOT -VERSION_NAME=0.8.1-SNAPSHOT +version=0.9.0 +VERSION_NAME=0.9.0 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 33811fd4573d15dedc0c070f534b10cf0e75be2a Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 13 Feb 2018 14:04:55 -0800 Subject: [PATCH 061/165] [Gradle Release Plugin] - new version commit: 'v0.9.1-SNAPSHOT'. --- CHANGELOG.md | 3 +++ gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 290219e2..604642e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.9.1 - TBD +------------ + v0.9.0 - 02/13/2018 ------------ diff --git a/gradle.properties b/gradle.properties index 9ccb338c..81dbc287 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Tue, 13 Feb 2018 14:01:15 -0800 +#Tue, 13 Feb 2018 14:04:55 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.9.0 -VERSION_NAME=0.9.0 +version=0.9.1-SNAPSHOT +VERSION_NAME=0.9.1-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 080be0ff274b4cd1e54c6c28eec37f90c0b197d0 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 15 Mar 2018 16:44:39 -0700 Subject: [PATCH 062/165] Add task relationship to fix release notes --- gradle/github-release.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index d5d7efee..13030851 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -88,11 +88,13 @@ task updateNewVersionChangelog() << { } } -afterReleaseBuild.dependsOn(":core-android:uploadArchives", ":rides-android:uploadArchives") updateVersion.dependsOn ":githubRelease" +githubRelease.mustRunAfter ":createReleaseTag" +githubRelease.dependsOn project(":samples").subprojects.collect { it.path + ":githubReleaseZip" } +updateReleaseVersionChangelog.mustRunAfter ":afterReleaseBuild" preTagCommit.dependsOn ':updateReleaseVersionChangelog' +updateNewVersionChangelog.mustRunAfter ":updateVersion" commitNewVersion.dependsOn ':updateNewVersionChangelog' -githubRelease.dependsOn project(":samples").subprojects.collect { it.path + ":githubReleaseZip" } release { failOnCommitNeeded = false From 5dbfa15b51cf6430fc0492a565f53891b4e00bf0 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 16 Mar 2018 09:23:06 -0700 Subject: [PATCH 063/165] Fixing Artifact creation of sample apps" --- build.gradle | 6 +- gradle/github-release.gradle | 66 +++++++++++----------- samples/login-sample/build.gradle | 1 + samples/request-button-sample/build.gradle | 1 + 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/build.gradle b/build.gradle index 6f0bdca3..a2c32b11 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ */ buildscript { - apply from: 'gradle/dependencies.gradle' + apply from: rootProject.file('gradle/dependencies.gradle') repositories { jcenter() @@ -32,5 +32,5 @@ task wrapper(type: Wrapper) { distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" } -apply from: 'gradle/github-release.gradle' -apply from: 'gradle/verification.gradle' \ No newline at end of file +apply from: rootProject.file('gradle/github-release.gradle') +apply from: rootProject.file('gradle/verification.gradle') \ No newline at end of file diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 13030851..ca3a5660 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -120,44 +120,44 @@ github { } subprojects { - configure(subprojects.findAll {it.parent.name == 'samples'}) { - task githubReleaseZip(type: Zip) << { - version = "v${rootProject.version}" - - from('.') { - filesNotMatching("**/*.png") { - filter { String line -> - line.replaceAll("compile project\\(':rides-android'\\)", - "compile '${groupId}:rides-android:${rootProject.version}'") - } - filter { String line -> - line.replaceAll("compile project\\(':core-android'\\)", - "compile '${groupId}:core-android:${rootProject.version}'") + if (parent.name == 'samples') { + task githubReleaseZip(type: Zip) { + version = "v${rootProject.version}" + + from('.') { + filesNotMatching("**/*.png") { + filter { String line -> + line.replaceAll("compile project\\(':rides-android'\\)", + "compile '${GROUP}:rides-android:${rootProject.version}'") + } + filter { String line -> + line.replaceAll("compile project\\(':core-android'\\)", + "compile '${GROUP}:core-android:${rootProject.version}'") + } } + into '.' + exclude 'build' + exclude '*.iml' } - 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', '') + from(rootProject.projectDir.absolutePath) { + include 'gradle/' + include 'gradlew' + include 'gradlew.bat' + include 'LICENSE' + into '.' } - filter { String line -> - line.replaceAll('-SNAPSHOT', '') + + from('build/poms') { + include 'pom-default.xml' + rename { String fileName -> + fileName.replaceAll('-default', '') + } + filter { String line -> + line.replaceAll('-SNAPSHOT', '') + } + into '.' } - into '.' } - } } } diff --git a/samples/login-sample/build.gradle b/samples/login-sample/build.gradle index 013a22d2..4b3f0ef6 100644 --- a/samples/login-sample/build.gradle +++ b/samples/login-sample/build.gradle @@ -15,6 +15,7 @@ */ buildscript { + apply from: rootProject.file('gradle/dependencies.gradle') repositories { jcenter() google() diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle index 233c84d4..4c8a6145 100644 --- a/samples/request-button-sample/build.gradle +++ b/samples/request-button-sample/build.gradle @@ -15,6 +15,7 @@ */ buildscript { + apply from: rootProject.file('gradle/dependencies.gradle') repositories { jcenter() google() From 442805c6bab4672b0d37971b48fbd7556955b10e Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 16 Mar 2018 09:24:51 -0700 Subject: [PATCH 064/165] Add changelog note --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 604642e9..6d320d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ v0.9.1 - TBD ------------ +### Fixed + - [Issue #115](https://github.com/uber/rides-android-sdk/issues/115) Release Script is creating invalid release notes/download artifacts. + + v0.9.0 - 02/13/2018 ------------ From 9ca9a8ae7f4385784ba711439fd0b09884b6fb5f Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 16 Mar 2018 16:54:11 -0700 Subject: [PATCH 065/165] Changing artifacts zip to apk --- gradle/github-release.gradle | 85 ++++++++++-------------------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index ca3a5660..8805dd31 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -30,14 +30,14 @@ def isReleaseBuild() { def generateReleaseNotes() { def changelogSnippet = generateChangelogSnippet() - def model = [title : "Uber Rides Android SDK (Beta) v${rootProject.version}", + def releaseVersion = rootProject.version.replaceAll('-SNAPSHOT', '') + def model = [title : "Uber Rides Android SDK (Beta) v${releaseVersion}", date : DateGroovyMethods.format(new Date(), 'MM/dd/yyyy'), snippet: changelogSnippet, assets : project.samples.collect { [ title : project(it).name, - download : GITHUB_DOWNLOAD_PREFIX + "v${rootProject.version}/" - + project(it).name + "-v${rootProject.version}.zip", + download : GITHUB_DOWNLOAD_PREFIX + "v${releaseVersion}/${project(it).name}-debug.apk", description: project(it).description, ] }] @@ -88,9 +88,27 @@ task updateNewVersionChangelog() << { } } +task configureGithub() << { + github { + owner = GITHUB_OWNER + repo = GITHUB_REPO + token = "${GITHUB_TOKEN}" + tagName = "v${rootProject.version}" + targetCommitish = GITHUB_BRANCH + name = "v${rootProject.version}" + body = generateReleaseNotes() + assets = project.samples.collect { + "${project(it).buildDir.absolutePath}/outputs/apk/${project(it).name}-debug.apk" + } + } +} + +githubRelease.dependsOn ":configureGithub" +configureGithub.mustRunAfter ":createReleaseTag" + updateVersion.dependsOn ":githubRelease" -githubRelease.mustRunAfter ":createReleaseTag" -githubRelease.dependsOn project(":samples").subprojects.collect { it.path + ":githubReleaseZip" } +githubRelease.mustRunAfter ":configureGithub" + updateReleaseVersionChangelog.mustRunAfter ":afterReleaseBuild" preTagCommit.dependsOn ':updateReleaseVersionChangelog' updateNewVersionChangelog.mustRunAfter ":updateVersion" @@ -104,60 +122,3 @@ release { tagTemplate = 'v$version' versionProperties = ['VERSION_NAME'] } - -github { - owner = GITHUB_OWNER - repo = GITHUB_REPO - token = "${GITHUB_TOKEN}" - tagName = "v${rootProject.version}" - targetCommitish = GITHUB_BRANCH - name = "v${rootProject.version}" - body = generateReleaseNotes() - assets = project.samples.collect { - project(it).buildDir.absolutePath + "/distributions/" + project(it).name + - "-v${rootProject.version}.zip" - } -} - -subprojects { - if (parent.name == 'samples') { - task githubReleaseZip(type: Zip) { - version = "v${rootProject.version}" - - from('.') { - filesNotMatching("**/*.png") { - filter { String line -> - line.replaceAll("compile project\\(':rides-android'\\)", - "compile '${GROUP}:rides-android:${rootProject.version}'") - } - filter { String line -> - line.replaceAll("compile project\\(':core-android'\\)", - "compile '${GROUP}:core-android:${rootProject.version}'") - } - } - 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 '.' - } - } - } -} From 7aa9e36ed0bdb48d2fe66433a23a5dd999b62e89 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 16 Mar 2018 17:13:26 -0700 Subject: [PATCH 066/165] Fixing missing upload archives --- gradle/github-release.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle/github-release.gradle b/gradle/github-release.gradle index 8805dd31..bee631e5 100644 --- a/gradle/github-release.gradle +++ b/gradle/github-release.gradle @@ -109,6 +109,8 @@ configureGithub.mustRunAfter ":createReleaseTag" updateVersion.dependsOn ":githubRelease" githubRelease.mustRunAfter ":configureGithub" +afterReleaseBuild.dependsOn(":core-android:uploadArchives", ":rides-android:uploadArchives") + updateReleaseVersionChangelog.mustRunAfter ":afterReleaseBuild" preTagCommit.dependsOn ':updateReleaseVersionChangelog' updateNewVersionChangelog.mustRunAfter ":updateVersion" From 23d02c0f6b5369f5e66612eb62e8603eb34ac2a2 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 20 Mar 2018 10:39:42 -0700 Subject: [PATCH 067/165] Update Java SDK dependency to 0.8.0 --- CHANGELOG.md | 1 + gradle/dependencies.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d320d89..90cf081b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ v0.9.1 - TBD ### Fixed - [Issue #115](https://github.com/uber/rides-android-sdk/issues/115) Release Script is creating invalid release notes/download artifacts. + - Updated to Java SDK 0.8.0 to fix Token Refresh NPE v0.9.0 - 02/13/2018 diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 9444905f..df024897 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -17,7 +17,7 @@ def versions = [ androidTest: '0.5', support: '26.1.0', - uberJava: '0.7.0', + uberJava: '0.8.0', ] def build = [ From 90fa6f176ef930c0f5b8e2cdcd23065bde7b0cc7 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 20 Mar 2018 11:16:41 -0700 Subject: [PATCH 068/165] [Gradle Release Plugin] - pre tag commit: 'v0.9.1'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90cf081b..27823cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.9.1 - TBD +v0.9.1 - 03/20/2018 ------------ ### Fixed diff --git a/gradle.properties b/gradle.properties index 81dbc287..10950d17 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Tue, 13 Feb 2018 14:04:55 -0800 +#Tue, 20 Mar 2018 11:13:45 -0700 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.9.1-SNAPSHOT -VERSION_NAME=0.9.1-SNAPSHOT +version=0.9.1 +VERSION_NAME=0.9.1 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 036cd13e9216d598352344a6b85c9bc68eb7af68 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 20 Mar 2018 11:16:52 -0700 Subject: [PATCH 069/165] [Gradle Release Plugin] - new version commit: 'v0.9.2-SNAPSHOT'. --- CHANGELOG.md | 3 +++ gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27823cbb..0a238c60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.9.2 - TBD +------------ + v0.9.1 - 03/20/2018 ------------ diff --git a/gradle.properties b/gradle.properties index 10950d17..6783f521 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Tue, 20 Mar 2018 11:13:45 -0700 +#Tue, 20 Mar 2018 11:16:52 -0700 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.9.1 -VERSION_NAME=0.9.1 +version=0.9.2-SNAPSHOT +VERSION_NAME=0.9.2-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From b9cd5acf8cdcbba07050142d3c1d082bcc7036e0 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Thu, 22 Mar 2018 23:25:12 -0700 Subject: [PATCH 070/165] Fix snapshots auto-upload --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5815778f..cafbfe37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,8 @@ after_success: env: global: - - secure: "FrNGjknmIfrU2/6UHWd9toPdLcfXs07RHOgk1NdKKK0DoMTsz2ovbaftotZJ2Uk2Y2GqnTITdhd+AbwfLb/eCAymSN+FXDrZ69Yv22D5dmMmBlFNI7txb4s7cAobtOb80aZyOJpafqdxhgvDHldHereOLNl4cSWRw5behg2M+82vckDoXPmNT/h1ebGdxsV1R5t6Tkg0OEuvryytQr3OuPRC8RbuxwSefqeIe/qsTGxEkR8OWc8B+pjdt20bYt+FXezG8W371Zb72dJUYfB5tl2t/3RG5FTDGdSh7niC37MPHy/JTunUR+hTcBjBsJDUFV9aPmulKmOLHaEKP9tM5H8MGdQMyC9fIt4Qp89TytkVLoGtPXE9mDAv5oKOwqz779os7FSNQ9BswoKIYtTpsNA9KxOLlghpnBXUA8rTyCNNgjaseOw4W8aF+JsvUzdw37eVfGlIOpZiy2t+mHPZU+JpzPt0Ydr+UCOWVXL/9B7mA/B/53Y/WA5YQ3yitwdXzkSpdJ0IUePRdcUfTK4LUMUH8WgxjVKLpECGRq5dk+IXHGkFihXjzRbQiaNox+DroXzjgBqoTAZ3HgRJ3PvSWrIlzWO+eaK5G3ACeDLthPpGIGv4AUebH89hZS9s3iO/1tKG58xlZayl+19FtxkYSaR/0z3UlBDcsohJ2+L6j0w=" - - secure: "QIJXhUaNBAgAfEwyPtoh1Lrb9VhPKVFbqmmCpywTTC/hHzj9sl84ZiCraQ4W3yWg81J2CvIMWh3qy5q03BsJ6ZZ7HxBq5Z72Lm4i1ckXoZMTOPc0ppvE6Ep3bNXSG74yk6oBc+zkrNqIkYIZrbiyutZwqKJmlbmSU16Zq6E3suy47gigZnLYo/yIBHJ14slLcPJYaxwA/XuIl4ed+7oklEtSwHZ4plcIPNaeDTBaAZ5Ws4gIxteNQrVbRn8BWQf74iH/XWBj8HPsC4MQzRWi+HjNN95b/U9rAO8EGVUy1xamotRJJu8hdLF4Wynqj0MyxAQsmqmRJCISmcYXgxQ0Ig3DaR44NEfBNuxeKpR5dKF4qgtlCjSap5JArQAc/jkr0E+HtH4TPIgKdEkNm+K65Txpsw0Z5SCaURPBrsyiWK2V3Dw854qQBAw4VeeDgh63ysBjEEBsUqthRUlCgIHeHOagUPACByltzfI4Ha2Ld0/AAMx/w59+1sBgYFqiUT/ac0UHqvFkOEGbw5874rIwZrkNvwoiApTHV50AWYU5ET6UsNGrVGC8t4H792XM8hmqodxAovuyBLVvgxorLpnH76q8mnZFMlS1D5dxu+lz6p3oDchf4dl3QaMeldAx9P8+/Fky7y0sqiFgJLVQSo5nUEwjwDpt1IhSb8R2mj1Rv/A=" + - secure: "g5g+VmfqVF7PMuTl00NUQpX4pFohsEVb/SUK3adFWdT5SKOZgi28ArwUEmCMbG/he5UyyQJkv38CKfa+6jZM1cDDNkICGEjM3YCImJyCDpUKLKLFlsvDtAPK7H8rUpLgmGOHQONObtJbhtkmWg+0fr6TRj+yltZZl/6dKZ7Im4aeeMDE03Hy8MubvxRqANF3lT1bJMLUTIAP/gHSD46AKhlbnUJHW5QXqKJqMbtt3nyZNZ3aWPVMoGc+zTbpMWD6dvCH0Etc/nEatMINLunHEUf59CVEiCmQSHrWHsyDn75OiVCJUEzj9euTKE1Kz5OjKrPNYPlW1V840EOLdr/I3rz50gFmMjSyNZcd6D4W6jygOC0QDm57ISHO/Jtl4iLaPzqKMRT4daiyqWZJrnFB8Xlt8CjCxxxXT0A1ZXgro1Auoaa4OVg53Ey40CuRABHbVu6KnskcT5A52XHlDxh3wi5LnPaLH5LzB5/erFzzgLn0GPYTVKInKen6/cY+/+h84rMgS8VkKegl3oUuz1Kzej1GkxnFzdwm3j+1CuDTkadmlpn92+N6wXlG628cs7e93m0QhxXsd+mIaAwyicQeelsOeaAXTp72xfvXE7RQA2YNXjAqVoXu0kTWlXs2aDlm3HHOS4FR0kR96TJarqpinD+zAzEseaP4rMCgqdOsKok=" + - secure: "cp+/y+Zw6ijlK/JBigffDVhQyVhZTfSfgkkM5V0USlJIS2c3N7e1vruiXIcRLEIfzgTbU0veVn37NDIDMkoUDlUY9q2p0uM+xwoO8URYK/SqOhevFSd+QgMX9QwnaV5reD6aZzrz/SI6AbA6hAmIRIXZGkxRfgsB1nJN4m1Fwiwib2IXPHmBa+8d1vS7yU36FrY/3fHthgz1T1M+VDVFmbIGbBqCj5GR6CF+TcDcEQW3UvH671s7IAzuY8VzopeQjaqHE+U+8wrkg0mmiX6/B3a9nH6G/rfaeLTpomb0QCojk+W8en85f7KdK6/7SEiKU0NJmJ62ccc8n0h1VxhWYx/r2S2nVM8FfVuOTOPWuGZU0ULI1ylqAYgppBMp49eOlAJV5QzyBB7Q+py20wEPBisIlvZXLytNm7RWojiTt1wm2PaEhFzYmfPgLlViFDv7N+hLhxproDpw+c1XhhM2ZPyxW8oUriRxyMRqXkJClEgNg+0X9IyFzGc93S7yw0u5SMwhC735vjk3G4sSL/bZjre5zSc/XXM+HqIzWP+KUwRZqdcwW7uuGMB8LUWAHiCrZUuCRL25cisKW/xArH3Sg4nrFG3VP+8yMgRRVJWMtCandSzsJcBmiFPEqsb12eTo2hQfJzoKMX9HcOitfkjn1U1zsJm1ycsG61prF7fhdHg=" branches: except: From 1141c70fc26d48eb727b09f9b7747f1837cbae83 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 30 Mar 2018 10:53:12 -0700 Subject: [PATCH 071/165] Fix redirect URI example in README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c41e6d90..2dc1505b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Uber Rides Android SDK (beta) [![Build Status](https://travis-ci.org/uber/rides-android-sdk.svg?branch=master)](https://travis-ci.org/uber/rides-android-sdk) +edi# Uber Rides Android SDK (beta) [![Build Status](https://travis-ci.org/uber/rides-android-sdk.svg?branch=master)](https://travis-ci.org/uber/rides-android-sdk) Official Android SDK to support: - Ride Request Button @@ -244,7 +244,7 @@ With Version 0.8 and above of the SDK, the redirect URI is more strongly enforce standards [IETF RFC](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12). The SDK will automatically created a redirect URI to be used in the oauth callbacks with -the format "applicationId.uberauth", ex "com.example.uberauth". **This URI must be registered in +the format "applicationId.uberauth://redirect", ex "com.example.app.uberauth://redirect". **This URI must be registered in the [developer dashboard](https://developer.uber.com/dashboard)** If this differs from the previous specified redirect URI configured in the SessionConfiguration, @@ -255,7 +255,7 @@ there are a few options. ```java SessionConfiguration config = new SessionConfiguration.Builder() - .setRedirectUri("com.example.app.uberauth") + .setRedirectUri("com.example.app.uberauth://redirecturi") .build(); ``` @@ -284,7 +284,7 @@ manager should indicate the SDK is operating in the Authorization Code Flow. ```java SessionConfiguration config = new SessionConfiguration.Builder() - .setRedirectUri("example.com/redirect") //Where this is your configured server + .setRedirectUri("https://example.com/redirect") //Where this is your configured server .build(); loginManager.setAuthCodeEnabled(true); @@ -293,7 +293,7 @@ loginManager.login(this); ``` Once the code is exchanged, the server should redirect to a URI in the standard OAUTH format of - `com.example.uberauth://redirect#access_token=ACCESS_TOKEN&token_type=Bearer&expires_in=TTL&scope=SCOPES` + `com.example.app.uberauth://redirect#access_token=ACCESS_TOKEN&token_type=Bearer&expires_in=TTL&scope=SCOPES` for the SDK to receive the access token and continue operation.`` From 02f14ca7966b45ea5286acb73b36b4f8a28b20aa Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 30 Mar 2018 10:54:03 -0700 Subject: [PATCH 072/165] Remove typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2dc1505b..a4b65f3f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -edi# Uber Rides Android SDK (beta) [![Build Status](https://travis-ci.org/uber/rides-android-sdk.svg?branch=master)](https://travis-ci.org/uber/rides-android-sdk) +# Uber Rides Android SDK (beta) [![Build Status](https://travis-ci.org/uber/rides-android-sdk.svg?branch=master)](https://travis-ci.org/uber/rides-android-sdk) Official Android SDK to support: - Ride Request Button From 2fc41a99f14045fae4ea0154b439d65d6f6df51b Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 30 Mar 2018 10:56:45 -0700 Subject: [PATCH 073/165] Fix redirect in example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4b65f3f..c04da18f 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ there are a few options. ```java SessionConfiguration config = new SessionConfiguration.Builder() - .setRedirectUri("com.example.app.uberauth://redirecturi") + .setRedirectUri("com.example.app.uberauth://redirect") .build(); ``` From 50246ff35cfd4d541cbb0d2e1e21e8e39f607730 Mon Sep 17 00:00:00 2001 From: Martin Groen Date: Sun, 1 Apr 2018 17:30:02 +1000 Subject: [PATCH 074/165] Rename `setAuthCodeEnabled` to `setAuthCodeFlowEnabled` On 0.9.1 this seems to be the method to use --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c04da18f..f88287bc 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ SessionConfiguration config = new SessionConfiguration.Builder() .setRedirectUri("https://example.com/redirect") //Where this is your configured server .build(); -loginManager.setAuthCodeEnabled(true); +loginManager.setAuthCodeFlowEnabled(true); loginManager.login(this); ``` @@ -303,7 +303,7 @@ loginManager.login(this); The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if SSO is unavailable, fallback to Implicit Grant if privileged scopes are not requested, otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager -.setAuthCodeEnabled(true)` to prevent the redirect to the Play Store. Implicit Grant will allow +.setAuthCodeFlowEnabled(true)` to prevent the redirect to the Play Store. Implicit Grant will allow access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). From 035cec30b47b994153e4f30a280016bef5b2bd66 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Mon, 13 Aug 2018 17:06:56 -0400 Subject: [PATCH 075/165] Update travis config to accept updated license --- .travis.yml | 2 +- .../java/com/uber/sdk/android/core/utils/Preconditions.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cafbfe37..169982cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_install: - echo "$LICENSES_HASH" > "$ANDROID_HOME/licenses/android-sdk-license" - echo "$LICENSES_HASH_TWO" >> "$ANDROID_HOME/licenses/android-sdk-license" # Install the rest of tools (e.g., avdmanager) - - sdkmanager tools + - yes | sdkmanager tools # Install the system image - sdkmanager "system-images;android-18;default;armeabi-v7a" # Create and start emulator for the script. Meant to race the install task. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java index d466ce5d..10a4cf73 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java @@ -46,7 +46,6 @@ public static void checkState(final boolean expression, @NonNull String errorMes } } - /** * Ensures the value is not null. * From 19a64b25c926a866ccfe39d5391977b378e7efd7 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Tue, 7 Aug 2018 17:01:14 -0400 Subject: [PATCH 076/165] Add new method setForceAuthCodeFlowEnabled to LoginManager --- README.md | 14 ++- .../core/auth/LegacyUriRedirectHandler.java | 2 +- .../sdk/android/core/auth/LoginManager.java | 37 ++++++ .../auth/LegacyUriRedirectHandlerTest.java | 31 +++++ .../android/core/auth/LoginManagerTest.java | 110 +++++++++++++++++- 5 files changed, 184 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f88287bc..b2f67e4f 100644 --- a/README.md +++ b/README.md @@ -287,14 +287,14 @@ SessionConfiguration config = new SessionConfiguration.Builder() .setRedirectUri("https://example.com/redirect") //Where this is your configured server .build(); -loginManager.setAuthCodeFlowEnabled(true); +loginManager.setAuthCodeFlowEnabled(true); // or loginManager.setForceAuthCodeFlowEnabled(true) (see below) loginManager.login(this); ``` Once the code is exchanged, the server should redirect to a URI in the standard OAUTH format of - `com.example.app.uberauth://redirect#access_token=ACCESS_TOKEN&token_type=Bearer&expires_in=TTL&scope=SCOPES` - for the SDK to receive the access token and continue operation.`` + `com.example.app.uberauth://redirect#access_token=ACCESS_TOKEN&token_type=Bearer&expires_in=TTL&scope=SCOPES&refresh_token=REFRESH_TOKEN` + for the SDK to receive the access token and continue operation.`` ##### Authorization Code Flow @@ -302,9 +302,11 @@ loginManager.login(this); The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if SSO is unavailable, fallback to Implicit Grant if privileged scopes are not requested, -otherwise redirect to the Play Store. If Authorization Code Grant is required, set `LoginManager -.setAuthCodeFlowEnabled(true)` to prevent the redirect to the Play Store. Implicit Grant will allow -access to all non-privileged scopes, where as the other two both grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). +otherwise redirect to the Play Store. If you require Authorization Code Grant, you have two options +for overriding this behavior: set `LoginManager.setAuthCodeFlowEnabled(true)` to use the Authorization Code Flow only for +requesting privileged scopes (and fallback to ImplicitGrant otherwise) or set `LoginManager.setForceAuthCodeFlowEnabled(true)` +to prefer the Authorization Code Flow regardless of scope. Either option will prevent the redirect to the Play Store. +Implicit Grant will allow access to all non-privileged scopes (and will not grant a refresh token), whereas the other options grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). #### Login Errors diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java index 8ec61f17..c53e92bf 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java @@ -71,7 +71,7 @@ private void initState(@NonNull Activity activity, @NonNull LoginManager loginMa mode = Mode.MISSING_REDIRECT; } else if (!generatedRedirectUri.equals(setRedirectUri) && !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri)) && - !loginManager.isAuthCodeFlowEnabled()) { + !loginManager.isAuthCodeFlowEnabled() && !loginManager .isForceAuthCodeFlowEnabled()) { mode = Mode.MISMATCHING_URI; } else { mode = Mode.OFF; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 0d218044..f2306474 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -97,6 +97,7 @@ public class LoginManager { private final LegacyUriRedirectHandler legacyUriRedirectHandler; private boolean authCodeFlowEnabled = false; + private boolean forceAuthCodeFlowEnabled = false; @Deprecated private boolean redirectForAuthorizationCode = false; @@ -181,6 +182,8 @@ public void login(final @NonNull Activity activity) { if (ssoDeeplink.isSupported()) { ssoDeeplink.execute(); + } else if (isForceAuthCodeFlowEnabled()) { + loginForAuthorizationCode(activity); } else if (!AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { loginForImplicitGrant(activity); } else if (isAuthCodeFlowEnabled()) { @@ -345,6 +348,37 @@ public boolean isAuthCodeFlowEnabled() { return authCodeFlowEnabled; } + /** + * Force the use of the Authorization Code Flow + * (See + * https://developer.uber.com/docs/authentication#section-step-one-authorize) + * instead of falling back to Implicit Grant (WebView) if the user doesn't have the Uber app installed regardless + * of the user's requested scopes. This takes precedence over {@link #setAuthCodeFlowEnabled(boolean)}. + * + * Requires that the app's backend system is configured to support this flow and the redirect + * URI is pointed correctly. + * + * + * @param forceAuthCodeFlowEnabled true for use of auth code flow, false to fallback to Implicit Grant + * @return this instace of {@link LoginManager} + */ + public LoginManager setForceAuthCodeFlowEnabled(boolean forceAuthCodeFlowEnabled) { + this.forceAuthCodeFlowEnabled = forceAuthCodeFlowEnabled; + return this; + } + + /** + * Indicates whether to force use of the Authorization Code Flow + * (See + * https://developer.uber.com/docs/authentication#section-step-one-authorize) + * instead of Implicit Grant (WebView) as a login mechanism in the event that the Uber app is not installed. + * + * @return true if Auth Code Flow is attempted before Implicit Grant, otherwise false + */ + public boolean isForceAuthCodeFlowEnabled() { + return this.forceAuthCodeFlowEnabled; + } + private void redirectToInstallApp(@NonNull Activity activity) { new SignupDeeplink(activity, sessionConfiguration.getClientId(), USER_AGENT).execute(); } @@ -392,6 +426,9 @@ private void handleResultCancelled( // User canceled login callback.onLoginCancel(); return; + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && isForceAuthCodeFlowEnabled()) { + loginForAuthorizationCode(activity); + return; } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { loginForImplicitGrant(activity); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java index 38a77e12..0e93bc0e 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java @@ -184,6 +184,20 @@ public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInDebug_ assertNoLogs(); } + @Test + public void handleInvalidState_withForceAuthCodeFlowAndMisMatchingRedirectUriInDebug_validState() { + when(sessionConfiguration.getRedirectUri()) + .thenReturn("com.example2.uberauth://redirect-uri"); + when(loginManager.isForceAuthCodeFlowEnabled()).thenReturn(true); + + assertThat(legacyUriRedirectHandler.checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + + assertNoDialogShown(); + assertNoLogs(); + } + @Test public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInRelease_validState() { when(sessionConfiguration.getRedirectUri()) @@ -199,6 +213,23 @@ public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInReleas assertNoLogs(); } + @Test + public void handleInvalidState_withForceAuthCodeFlowAndMisMatchingRedirectUriInRelease_validState() { + when(sessionConfiguration.getRedirectUri()) + .thenReturn("com.example2.uberauth://redirect-uri"); + when(loginManager.isForceAuthCodeFlowEnabled()).thenReturn(true); + applicationInfo.flags = 0; + + assertThat(legacyUriRedirectHandler. + + checkValidState(activity, + loginManager)).isTrue(); + assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); + + assertNoDialogShown(); + assertNoLogs(); + } + @Test public void isLegacyMode_uninitialized_validState() { assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 36947aca..788ed3d1 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -188,7 +188,7 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte } @Test - public void loginWithoutAppInstalledGeneralScopes_shouldLaunchWebView() { + public void loginWithoutAppInstalledGeneralScopes_shouldLaunchImplicitGrant() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); @@ -204,11 +204,65 @@ public void loginWithoutAppInstalledGeneralScopes_shouldLaunchWebView() { assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); - SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() + final Intent capturedIntent = intentCaptor.getValue(); + final ResponseType responseType = (ResponseType) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + final SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); + assertThat(responseType).isEqualTo(ResponseType.TOKEN); assertThat(configuration.getScopes()).containsAll(GENERAL_SCOPES); } + @Test + public void loginWithoutAppInstalledGeneralScopesAndForceAuthCodeFlowEnabled_shouldLaunchAuthCodeFlow() { + sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration); + + stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); + + loginManager.setForceAuthCodeFlowEnabled(true); + loginManager.login(activity); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); + + final Intent capturedIntent = intentCaptor.getValue(); + final ResponseType responseType = (ResponseType) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + final SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() + .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); + assertThat(responseType).isEqualTo(ResponseType.CODE); + assertThat(configuration.getScopes()).containsAll(GENERAL_SCOPES); + } + + @Test + public void loginWithoutAppInstalledPrivilegedScopesAndAuthCodeFlowEnabled_shouldLaunchAuthCodeFlow() { + stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); + + loginManager.setAuthCodeFlowEnabled(true); + loginManager.login(activity); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); + + final Intent capturedIntent = intentCaptor.getValue(); + final ResponseType responseType = (ResponseType) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + final SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() + .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); + assertThat(responseType).isEqualTo(ResponseType.CODE); + assertThat(configuration.getScopes()).containsAll(MIXED_SCOPES); + } + @Test public void loginWithoutAppInstalledPrivilegedScopes_shouldLaunchAppInstall() { final Activity activity = spy(Robolectric.setupActivity(Activity.class)); @@ -342,7 +396,7 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_should } @Test - public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImplicitGrant() { + public void onActivityResult_whenUnavailableAndGeneralScopes_shouldTriggerImplicitGrant() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); @@ -367,6 +421,56 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerImp assertThat(loginConfiguration.getScopes()).containsAll(GENERAL_SCOPES); } + @Test + public void onActivityResult_whenUnavailableAndPrivilegedScopesForceAuthCodeFlowEnabled_shouldTriggerAuthorizationCode() { + Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); + + loginManager.setForceAuthCodeFlowEnabled(true); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + + verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE_LOGIN_DEFAULT)); + + final Intent capturedIntent = intentCaptor.getValue(); + final ResponseType responseType = (ResponseType) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); + + assertThat(responseType).isEqualTo(ResponseType.CODE); + assertThat(loginConfiguration.getScopes()).containsAll(MIXED_SCOPES); + } + + @Test + public void onActivityResult_whenUnavailableAndGeneralScopesForceAuthCodeFlowEnabled_shouldTriggerAuthorizationCode() { + sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration); + + Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); + + loginManager.setForceAuthCodeFlowEnabled(true); + loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); + + verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE_LOGIN_DEFAULT)); + + final Intent capturedIntent = intentCaptor.getValue(); + final ResponseType responseType = (ResponseType) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent + .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); + + assertThat(responseType).isEqualTo(ResponseType.CODE); + assertThat(loginConfiguration.getScopes()).containsAll(GENERAL_SCOPES); + } + @Test public void isAuthenticated_withServerToken_true() { when(accessTokenStorage.getAccessToken()).thenReturn(null); From 74de131010821d6af426abc536a3d97f27f89833 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Thu, 9 Aug 2018 11:50:38 -0400 Subject: [PATCH 077/165] remove new method, modify setAuthCodeFlow behavior to prefer Auth Code Flow over Implicit Grant --- README.md | 8 ++- .../core/auth/LegacyUriRedirectHandler.java | 2 +- .../sdk/android/core/auth/LoginManager.java | 52 +++---------------- .../auth/LegacyUriRedirectHandlerTest.java | 31 ----------- .../android/core/auth/LoginManagerTest.java | 49 +++++------------ 5 files changed, 25 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index b2f67e4f..7bfc6ec9 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ SessionConfiguration config = new SessionConfiguration.Builder() .setRedirectUri("https://example.com/redirect") //Where this is your configured server .build(); -loginManager.setAuthCodeFlowEnabled(true); // or loginManager.setForceAuthCodeFlowEnabled(true) (see below) +loginManager.setAuthCodeFlowEnabled(true); loginManager.login(this); ``` @@ -302,10 +302,8 @@ loginManager.login(this); The default behavior of calling `LoginManager.login(activity)` is to activate Single Sign On, and if SSO is unavailable, fallback to Implicit Grant if privileged scopes are not requested, -otherwise redirect to the Play Store. If you require Authorization Code Grant, you have two options -for overriding this behavior: set `LoginManager.setAuthCodeFlowEnabled(true)` to use the Authorization Code Flow only for -requesting privileged scopes (and fallback to ImplicitGrant otherwise) or set `LoginManager.setForceAuthCodeFlowEnabled(true)` -to prefer the Authorization Code Flow regardless of scope. Either option will prevent the redirect to the Play Store. +otherwise redirect to the Play Store. If you require Authorization Code Grant, set `LoginManager.setAuthCodeFlowEnabled(true)` +to use the Authorization Code Flow as the fallback mechanism instead of Implicit Grant or redirecting to the Play Store (regardless of scope). Implicit Grant will allow access to all non-privileged scopes (and will not grant a refresh token), whereas the other options grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java index c53e92bf..8ec61f17 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java @@ -71,7 +71,7 @@ private void initState(@NonNull Activity activity, @NonNull LoginManager loginMa mode = Mode.MISSING_REDIRECT; } else if (!generatedRedirectUri.equals(setRedirectUri) && !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri)) && - !loginManager.isAuthCodeFlowEnabled() && !loginManager .isForceAuthCodeFlowEnabled()) { + !loginManager.isAuthCodeFlowEnabled()) { mode = Mode.MISMATCHING_URI; } else { mode = Mode.OFF; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index f2306474..7d7ba740 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -97,7 +97,6 @@ public class LoginManager { private final LegacyUriRedirectHandler legacyUriRedirectHandler; private boolean authCodeFlowEnabled = false; - private boolean forceAuthCodeFlowEnabled = false; @Deprecated private boolean redirectForAuthorizationCode = false; @@ -182,12 +181,10 @@ public void login(final @NonNull Activity activity) { if (ssoDeeplink.isSupported()) { ssoDeeplink.execute(); - } else if (isForceAuthCodeFlowEnabled()) { + } else if (isAuthCodeFlowEnabled()) { loginForAuthorizationCode(activity); } else if (!AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { loginForImplicitGrant(activity); - } else if (isAuthCodeFlowEnabled()) { - loginForAuthorizationCode(activity); } else { redirectToInstallApp(activity); } @@ -322,8 +319,9 @@ public boolean isRedirectForAuthorizationCode() { /** * Enable the use of the Authorization Code Flow - * (https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an - * installation prompt for the Uber app as a login fallback mechanism. + * (See + * https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an + * installation prompt for the Uber app or Implicit Grant (WebView) as a login fallback mechanism. * * Requires that the app's backend system is configured to support this flow and the redirect * URI is pointed correctly. @@ -339,8 +337,9 @@ public LoginManager setAuthCodeFlowEnabled(boolean authCodeFlowEnabled) { /** * Indicates the use of the Authorization Code Flow - * (https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an - * installation prompt for the Uber app as a login fallback mechanism. + * (See + * https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an + * installation prompt for the Uber app or Implicit Grant (WebView) as a login fallback mechanism. * * @return true if Auth Code Flow is enabled, otherwise false */ @@ -348,37 +347,6 @@ public boolean isAuthCodeFlowEnabled() { return authCodeFlowEnabled; } - /** - * Force the use of the Authorization Code Flow - * (See - * https://developer.uber.com/docs/authentication#section-step-one-authorize) - * instead of falling back to Implicit Grant (WebView) if the user doesn't have the Uber app installed regardless - * of the user's requested scopes. This takes precedence over {@link #setAuthCodeFlowEnabled(boolean)}. - * - * Requires that the app's backend system is configured to support this flow and the redirect - * URI is pointed correctly. - * - * - * @param forceAuthCodeFlowEnabled true for use of auth code flow, false to fallback to Implicit Grant - * @return this instace of {@link LoginManager} - */ - public LoginManager setForceAuthCodeFlowEnabled(boolean forceAuthCodeFlowEnabled) { - this.forceAuthCodeFlowEnabled = forceAuthCodeFlowEnabled; - return this; - } - - /** - * Indicates whether to force use of the Authorization Code Flow - * (See - * https://developer.uber.com/docs/authentication#section-step-one-authorize) - * instead of Implicit Grant (WebView) as a login mechanism in the event that the Uber app is not installed. - * - * @return true if Auth Code Flow is attempted before Implicit Grant, otherwise false - */ - public boolean isForceAuthCodeFlowEnabled() { - return this.forceAuthCodeFlowEnabled; - } - private void redirectToInstallApp(@NonNull Activity activity) { new SignupDeeplink(activity, sessionConfiguration.getClientId(), USER_AGENT).execute(); } @@ -426,17 +394,13 @@ private void handleResultCancelled( // User canceled login callback.onLoginCancel(); return; - } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && isForceAuthCodeFlowEnabled()) { + } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && isAuthCodeFlowEnabled()) { loginForAuthorizationCode(activity); return; } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) && !AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { loginForImplicitGrant(activity); return; - } else if (authenticationError.equals(AuthenticationError.UNAVAILABLE) - && isAuthCodeFlowEnabled()) { - loginForAuthorizationCode(activity); - return; } else if (AuthenticationError.INVALID_APP_SIGNATURE.equals(authenticationError)) { AppProtocol appProtocol = new AppProtocol(); String appSignature = appProtocol.getAppSignature(activity); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java index 0e93bc0e..38a77e12 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java @@ -184,20 +184,6 @@ public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInDebug_ assertNoLogs(); } - @Test - public void handleInvalidState_withForceAuthCodeFlowAndMisMatchingRedirectUriInDebug_validState() { - when(sessionConfiguration.getRedirectUri()) - .thenReturn("com.example2.uberauth://redirect-uri"); - when(loginManager.isForceAuthCodeFlowEnabled()).thenReturn(true); - - assertThat(legacyUriRedirectHandler.checkValidState(activity, - loginManager)).isTrue(); - assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); - - assertNoDialogShown(); - assertNoLogs(); - } - @Test public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInRelease_validState() { when(sessionConfiguration.getRedirectUri()) @@ -213,23 +199,6 @@ public void handleInvalidState_withAuthCodeFlowAndMisMatchingRedirectUriInReleas assertNoLogs(); } - @Test - public void handleInvalidState_withForceAuthCodeFlowAndMisMatchingRedirectUriInRelease_validState() { - when(sessionConfiguration.getRedirectUri()) - .thenReturn("com.example2.uberauth://redirect-uri"); - when(loginManager.isForceAuthCodeFlowEnabled()).thenReturn(true); - applicationInfo.flags = 0; - - assertThat(legacyUriRedirectHandler. - - checkValidState(activity, - loginManager)).isTrue(); - assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); - - assertNoDialogShown(); - assertNoLogs(); - } - @Test public void isLegacyMode_uninitialized_validState() { assertThat(legacyUriRedirectHandler.isLegacyMode()).isFalse(); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 788ed3d1..9138391e 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -214,14 +214,14 @@ public void loginWithoutAppInstalledGeneralScopes_shouldLaunchImplicitGrant() { } @Test - public void loginWithoutAppInstalledGeneralScopesAndForceAuthCodeFlowEnabled_shouldLaunchAuthCodeFlow() { + public void loginWithoutAppInstalledGeneralScopesAndAuthCodeFlowEnabled_shouldLaunchAuthCodeFlow() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - loginManager.setForceAuthCodeFlowEnabled(true); + loginManager.setAuthCodeFlowEnabled(true); loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -383,26 +383,14 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAut } @Test - public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_shouldError() { - Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - sessionConfiguration = sessionConfiguration.newBuilder().build(); - loginManager = new LoginManager(accessTokenStorage, callback, - sessionConfiguration) - .setAuthCodeFlowEnabled(false); - - loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); - - verify(callback).onLoginError(AuthenticationError.UNAVAILABLE); - } - - @Test - public void onActivityResult_whenUnavailableAndGeneralScopes_shouldTriggerImplicitGrant() { + public void onActivityResult_whenUnavailableAndGeneralScopesWithAuthCodeEnabled_shouldTriggerAuthorizationCode() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); + loginManager.setAuthCodeFlowEnabled(true); loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); @@ -417,42 +405,31 @@ public void onActivityResult_whenUnavailableAndGeneralScopes_shouldTriggerImplic final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); - assertThat(responseType).isEqualTo(ResponseType.TOKEN); + assertThat(responseType).isEqualTo(ResponseType.CODE); assertThat(loginConfiguration.getScopes()).containsAll(GENERAL_SCOPES); } @Test - public void onActivityResult_whenUnavailableAndPrivilegedScopesForceAuthCodeFlowEnabled_shouldTriggerAuthorizationCode() { + public void onActivityResult_whenUnavailableAndPrivilegedScopesNoRedirect_shouldError() { Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); + sessionConfiguration = sessionConfiguration.newBuilder().build(); + loginManager = new LoginManager(accessTokenStorage, callback, + sessionConfiguration) + .setAuthCodeFlowEnabled(false); - loginManager.setForceAuthCodeFlowEnabled(true); loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); - verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); - - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - - verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE_LOGIN_DEFAULT)); - - final Intent capturedIntent = intentCaptor.getValue(); - final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); - final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); - - assertThat(responseType).isEqualTo(ResponseType.CODE); - assertThat(loginConfiguration.getScopes()).containsAll(MIXED_SCOPES); + verify(callback).onLoginError(AuthenticationError.UNAVAILABLE); } @Test - public void onActivityResult_whenUnavailableAndGeneralScopesForceAuthCodeFlowEnabled_shouldTriggerAuthorizationCode() { + public void onActivityResult_whenUnavailableAndGeneralScopes_shouldTriggerImplicitGrant() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); Intent intent = new Intent().putExtra(EXTRA_ERROR, AuthenticationError.UNAVAILABLE.toStandardString()); - loginManager.setForceAuthCodeFlowEnabled(true); loginManager.onActivityResult(activity, REQUEST_CODE_LOGIN_DEFAULT, Activity.RESULT_CANCELED, intent); verify(callback, never()).onLoginError(AuthenticationError.UNAVAILABLE); @@ -467,7 +444,7 @@ public void onActivityResult_whenUnavailableAndGeneralScopesForceAuthCodeFlowEna final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); - assertThat(responseType).isEqualTo(ResponseType.CODE); + assertThat(responseType).isEqualTo(ResponseType.TOKEN); assertThat(loginConfiguration.getScopes()).containsAll(GENERAL_SCOPES); } From 6a908dc51894c2f9290a0ccc36da1b987a3be0c0 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Mon, 13 Aug 2018 14:14:56 -0700 Subject: [PATCH 078/165] update test name --- .../java/com/uber/sdk/android/core/auth/LoginManagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 9138391e..0bf72171 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -188,7 +188,7 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte } @Test - public void loginWithoutAppInstalledGeneralScopes_shouldLaunchImplicitGrant() { + public void loginWithoutAppInstalledGeneralScopesAndAuthCodeFlowDisabled_shouldLaunchImplicitGrant() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); From b335c2387a2b93c25fa2a2e37c4f9e97816e3f49 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Tue, 14 Aug 2018 13:15:00 -0400 Subject: [PATCH 079/165] Updating gradle distribution to 4.9 from 4.3.1 --- gradle/dependencies.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index df024897..32485ad1 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -21,7 +21,7 @@ def versions = [ ] def build = [ - gradleVersion: '4.3.1', + gradleVersion: '4.9', buildToolsVersion: '26.0.2', compileSdkVersion: 26, ci: 'true' == System.getenv('CI'), diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 702c4b68..bd24854f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip From 76434a61fde55d789de22980a0f4082287a4e1f5 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Tue, 14 Aug 2018 13:18:56 -0400 Subject: [PATCH 080/165] Updating SDK compiler and target version. --- gradle/dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 32485ad1..f3e06a8a 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -23,10 +23,10 @@ def versions = [ def build = [ gradleVersion: '4.9', buildToolsVersion: '26.0.2', - compileSdkVersion: 26, + compileSdkVersion: 27, ci: 'true' == System.getenv('CI'), minSdkVersion: 15, - targetSdkVersion: 26, + targetSdkVersion: 27, repositories: [ plugins: 'https://plugins.gradle.org/m2/' From bffb3b37b11abd85276fec2793ae8088bad69207 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Tue, 14 Aug 2018 13:33:21 -0400 Subject: [PATCH 081/165] Update gradle plugin from 2.3.0 to 3.1.0 Updated support library and build tools. Updated gradle dependency config from "compile" to "implementation", which required adding a few explicit dependencies. --- build.gradle | 1 + core-android/build.gradle | 27 +++++++++++------- gradle/dependencies.gradle | 6 ++-- rides-android/build.gradle | 32 ++++++++++++++-------- samples/login-sample/build.gradle | 13 +++++++-- samples/request-button-sample/build.gradle | 13 +++++++-- 6 files changed, 63 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index a2c32b11..afc8b43f 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ buildscript { apply from: rootProject.file('gradle/dependencies.gradle') repositories { + google() jcenter() maven { url deps.build.repositories.plugins } } diff --git a/core-android/build.gradle b/core-android/build.gradle index 955dbf24..698fe64b 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -17,6 +17,7 @@ buildscript { repositories { jcenter() + google() maven { url deps.build.repositories.plugins } } @@ -43,21 +44,27 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } dependencies { - compile (deps.uber.uberCore) { + implementation (deps.uber.uberCore) { exclude module: 'slf4j-log4j12' } - compile deps.support.chrometabs - compile deps.misc.jsr305 - compile deps.support.appCompat - compile deps.support.annotations - - testCompile deps.test.junit - testCompile deps.test.assertj - testCompile deps.test.mockito - testCompile deps.test.robolectric + implementation deps.misc.jsr305 + implementation deps.support.appCompat + implementation deps.support.annotations + implementation deps.support.chrometabs + + testImplementation deps.test.junit + testImplementation deps.test.assertj + testImplementation deps.test.mockito + testImplementation deps.test.robolectric } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index f3e06a8a..c40d944d 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -16,13 +16,13 @@ def versions = [ androidTest: '0.5', - support: '26.1.0', + support: '27.1.1', uberJava: '0.8.0', ] def build = [ gradleVersion: '4.9', - buildToolsVersion: '26.0.2', + buildToolsVersion: '27.0.3', compileSdkVersion: 27, ci: 'true' == System.getenv('CI'), minSdkVersion: 15, @@ -33,7 +33,7 @@ def build = [ ], gradlePlugins: [ - android: 'com.android.tools.build:gradle:2.3.0', + android: 'com.android.tools.build:gradle:3.1.0', release: 'net.researchgate:gradle-release:2.1.2', github: 'co.riiid:gradle-github-plugin:0.4.2', cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1', diff --git a/rides-android/build.gradle b/rides-android/build.gradle index 6d0007ef..a89395b5 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -17,6 +17,7 @@ buildscript { repositories { jcenter() + google() maven { url deps.build.repositories.plugins } } @@ -43,24 +44,31 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } dependencies { - compile project(':core-android') - - compile (deps.uber.uberRides) { + implementation (deps.uber.uberRides) { exclude module: 'slf4j-log4j12' } - compile deps.misc.jsr305 - compile deps.support.appCompat - compile deps.support.annotations + implementation project(':core-android') + + implementation deps.misc.jsr305 + implementation deps.support.appCompat + implementation deps.support.annotations + implementation deps.support.chrometabs - testCompile deps.test.junit - testCompile deps.test.assertj - testCompile deps.test.mockito - testCompile deps.test.robolectric - testCompile deps.test.guava - testCompile deps.test.wiremock + testImplementation deps.test.junit + testImplementation deps.test.assertj + testImplementation deps.test.mockito + testImplementation deps.test.robolectric + testImplementation deps.test.guava + testImplementation deps.test.wiremock } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/samples/login-sample/build.gradle b/samples/login-sample/build.gradle index 4b3f0ef6..194f45b1 100644 --- a/samples/login-sample/build.gradle +++ b/samples/login-sample/build.gradle @@ -42,11 +42,20 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + testOptions { + unitTests { + includeAndroidResources = true + } + } } dependencies { - compile project(':rides-android') - compile deps.support.appCompat + implementation (deps.uber.uberRides) { + exclude module: 'slf4j-log4j12' + } + implementation project(':core-android') + implementation project(':rides-android') + implementation deps.support.appCompat } /** diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle index 4c8a6145..b6d6cbe8 100644 --- a/samples/request-button-sample/build.gradle +++ b/samples/request-button-sample/build.gradle @@ -43,11 +43,20 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + testOptions { + unitTests { + includeAndroidResources = true + } + } } dependencies { - compile project(':rides-android') - compile deps.support.appCompat + implementation (deps.uber.uberRides) { + exclude module: 'slf4j-log4j12' + } + implementation project(':core-android') + implementation project(':rides-android') + implementation deps.support.appCompat } /** From 3aa705af1c5ac7572dd36df68cf44d6839828506 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Wed, 15 Aug 2018 16:04:28 -0400 Subject: [PATCH 082/165] Updated Javadoc task to reduce errors. Adding per-variant compiler classpath to the overall classpath here fixes most of the "cannot find symbol" errors generated during the `androidJavadoc` task. --- gradle/gradle-mvn-push.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/gradle-mvn-push.gradle b/gradle/gradle-mvn-push.gradle index 48bd47c8..d848eb31 100644 --- a/gradle/gradle-mvn-push.gradle +++ b/gradle/gradle-mvn-push.gradle @@ -120,7 +120,9 @@ def androidArtifactTasks() { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - exclude '**/internal/*' + android.libraryVariants.all { variant -> + classpath += variant.javaCompiler.classpath + } if (JavaVersion.current().isJava8Compatible()) { options.addStringOption('Xdoclint:none', '-quiet') From 6764b13cf205b1c95d7e7a38a40e78dfed6c2e07 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Fri, 3 Aug 2018 15:44:26 -0400 Subject: [PATCH 083/165] Adding support to SSO with Uber Eats --- .../sdk/android/core/SupportedAppType.java | 14 ++ .../sdk/android/core/auth/SsoDeeplink.java | 72 +++++--- .../sdk/android/core/utils/AppProtocol.java | 90 ++++++++-- .../core/auth/AccessTokenManagerTest.java | 5 +- .../android/core/auth/LoginManagerTest.java | 8 +- .../android/core/auth/SsoDeeplinkTest.java | 160 ++++++++---------- .../android/rides/RideRequestDeeplink.java | 5 +- .../rides/RideRequestDeeplinkTest.java | 24 +-- 8 files changed, 224 insertions(+), 154 deletions(-) create mode 100644 core-android/src/main/java/com/uber/sdk/android/core/SupportedAppType.java diff --git a/core-android/src/main/java/com/uber/sdk/android/core/SupportedAppType.java b/core-android/src/main/java/com/uber/sdk/android/core/SupportedAppType.java new file mode 100644 index 00000000..d31191df --- /dev/null +++ b/core-android/src/main/java/com/uber/sdk/android/core/SupportedAppType.java @@ -0,0 +1,14 @@ +package com.uber.sdk.android.core; + +import android.content.Context; + +/** + * List of Apps that may support functionality of the SDK. Not all functionality is supported by all apps. + *

+ * To check if this app is installed use + * {@link com.uber.sdk.android.core.utils.AppProtocol#isInstalled(Context, SupportedAppType)}. + */ +public enum SupportedAppType { + UBER, + UBER_EATS, +} diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 1bf1a42f..9ad955d2 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -29,17 +29,19 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.Log; - +import android.util.Pair; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.Deeplink; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.AppProtocol; -import com.uber.sdk.android.core.utils.PackageManagers; import com.uber.sdk.core.auth.Scope; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import static com.uber.sdk.android.core.SupportedAppType.UBER; +import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; import static com.uber.sdk.android.core.UberSdk.UBER_SDK_LOG_TAG; import static com.uber.sdk.android.core.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; @@ -54,7 +56,9 @@ public class SsoDeeplink implements Deeplink { public static final int DEFAULT_REQUEST_CODE = LoginManager.REQUEST_CODE_LOGIN_DEFAULT; @VisibleForTesting - static final int MIN_VERSION_SUPPORTED = 31302; + static final int MIN_UBER_RIDES_VERSION_SUPPORTED = 31302; + @VisibleForTesting + static final int MIN_UBER_EATS_VERSION_SUPPORTED = 983; private static final String URI_QUERY_CLIENT_ID = "client_id"; private static final String URI_QUERY_SCOPE = "scope"; @@ -63,25 +67,25 @@ public class SsoDeeplink implements Deeplink { private static final String URI_HOST = "connect"; private final Activity activity; + private final AppProtocol appProtocol; private final String clientId; private final Collection requestedScopes; private final Collection requestedCustomScopes; private final int requestCode; - AppProtocol appProtocol; - - SsoDeeplink( + private SsoDeeplink( @NonNull Activity activity, + @NonNull AppProtocol appProtocol, @NonNull String clientId, @NonNull Collection requestedScopes, @NonNull Collection requestedCustomScopes, int requestCode) { this.activity = activity; + this.appProtocol = appProtocol; this.clientId = clientId; this.requestCode = requestCode; this.requestedScopes = requestedScopes; this.requestedCustomScopes = requestedCustomScopes; - appProtocol = new AppProtocol(); } /** @@ -95,16 +99,13 @@ public void execute() { checkState(isSupported(), "Single sign on is not supported on the device. " + "Please install or update to the latest version of Uber app."); - Intent intent = new Intent(Intent.ACTION_VIEW); final Uri deepLinkUri = createSsoUri(); intent.setData(deepLinkUri); - for (String installedPackage : AppProtocol.UBER_PACKAGE_NAMES) { - if (PackageManagers.isPackageAvailable(activity, installedPackage)) { - intent.setPackage(installedPackage); - break; - } + PackageInfo installedPackage = appProtocol.getInstalledUberAppPackage(activity); + if (installedPackage != null) { + intent.setPackage(installedPackage.packageName); } activity.startActivityForResult(intent, requestCode); } @@ -112,7 +113,7 @@ public void execute() { private Uri createSsoUri() { String scopes = AuthUtils.scopeCollectionToString(requestedScopes); if (!requestedCustomScopes.isEmpty()) { - scopes = AuthUtils.mergeScopeStrings(scopes, + scopes = AuthUtils.mergeScopeStrings(scopes, AuthUtils.customScopeCollectionToString(requestedCustomScopes)); } return new Uri.Builder().scheme(Deeplink.DEEPLINK_SCHEME) @@ -131,24 +132,31 @@ private Uri createSsoUri() { */ @Override public boolean isSupported() { + Pair installedUberAppPackage = appProtocol.getInstalledUberApp(activity); + if (installedUberAppPackage == null) { + return false; + } - PackageInfo packageInfo = null; - for (String installedPackage : AppProtocol.UBER_PACKAGE_NAMES) { - if (PackageManagers.isPackageAvailable(activity, installedPackage)) { - packageInfo = PackageManagers.getPackageInfo(activity, installedPackage); - break; - } + SupportedAppType supportedApp = installedUberAppPackage.first; + int minimumVersion; + if (UBER == supportedApp) { + minimumVersion = MIN_UBER_RIDES_VERSION_SUPPORTED; + } else if (UBER_EATS == supportedApp) { + minimumVersion = MIN_UBER_EATS_VERSION_SUPPORTED; + } else { + return false; } - return (packageInfo != null) - && appProtocol.validateMinimumVersion(activity, packageInfo, MIN_VERSION_SUPPORTED) + PackageInfo packageInfo = installedUberAppPackage.second; + return packageInfo != null + && appProtocol.validateMinimumVersion(activity, packageInfo, minimumVersion) && appProtocol.validateSignature(activity, packageInfo.packageName); } public static class Builder { private final Activity activity; - + private AppProtocol appProtocol; private String clientId; private Collection requestedScopes; private Collection requestedCustomScopes; @@ -183,6 +191,12 @@ public Builder activityRequestCode(int requestCode) { return this; } + @VisibleForTesting + Builder appProtocol(@NonNull AppProtocol appProtocol) { + this.appProtocol = appProtocol; + return this; + } + public SsoDeeplink build() { checkNotNull(clientId, "Client Id must be set"); @@ -195,7 +209,17 @@ public SsoDeeplink build() { if (requestCode == DEFAULT_REQUEST_CODE) { Log.i(UBER_SDK_LOG_TAG, "Request code is not set, using default request code"); } - return new SsoDeeplink(activity, clientId, requestedScopes, requestedCustomScopes, requestCode); + + if (appProtocol == null) { + appProtocol = new AppProtocol(); + } + + return new SsoDeeplink(activity, + appProtocol, + clientId, + requestedScopes, + requestedCustomScopes, + requestCode); } } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index b92bdae0..46e37303 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -1,6 +1,5 @@ package com.uber.sdk.android.core.utils; -import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -9,32 +8,67 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.util.Base64; +import android.util.Pair; +import com.uber.sdk.android.core.SupportedAppType; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import static com.uber.sdk.android.core.SupportedAppType.UBER; +import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; public class AppProtocol { + @Deprecated public static final String[] UBER_PACKAGE_NAMES = - {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo", - "com.ubercab.presidio.development"}; + {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo", "com.ubercab.presidio.development"}; + + @VisibleForTesting + static final String[] RIDER_PACKAGE_NAMES = + {"com.ubercab.presidio.development", "com.ubercab.presidio.exo", "com.ubercab.presidio.app", "com.ubercab"}; + @VisibleForTesting + static final String[] EATS_PACKAGE_NAMES = + {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats"}; public static final String PLATFORM = "android"; private static final String UBER_RIDER_HASH = "411c40b31f6d01dac68d711df99b6eafeec8e73b"; + private static final String UBER_EATS_HASH = "ae0b86995f174533b423067837beba13d922fbb0"; private static final String HASH_ALGORITHM_SHA1 = "SHA-1"; private static final HashSet validAppSignatureHashes = buildAppSignatureHashes(); + @NonNull private static HashSet buildAppSignatureHashes() { HashSet set = new HashSet<>(); set.add(UBER_RIDER_HASH); + set.add(UBER_EATS_HASH); return set; } + /** + * @return Map of SupportedAppType and their respective package names. + */ + @NonNull + private static Map> getSupportedPackageNames() { + Map> packageNames = new HashMap<>(); + packageNames.put(UBER, Arrays.asList(RIDER_PACKAGE_NAMES)); + packageNames.put(UBER_EATS, Arrays.asList(EATS_PACKAGE_NAMES)); + return packageNames; + } + /** * Validates minimum version of app required or returns true if in debug. */ - public boolean validateMinimumVersion(Context context, PackageInfo packageInfo, int minimumVersion) { + public boolean validateMinimumVersion( + @NonNull Context context, @NonNull PackageInfo packageInfo, int minimumVersion) { if (isDebug(context)) { return true; } @@ -42,20 +76,47 @@ public boolean validateMinimumVersion(Context context, PackageInfo packageInfo, return packageInfo.versionCode >= minimumVersion; } + /** + * @deprecated Use {@link #isInstalled(Context, SupportedAppType)}. + */ + @Deprecated public boolean isUberInstalled(@NonNull Context context) { - return getInstalledUberAppPackage(context) != null; + return isInstalled(context, UBER); + } + + /** + * Check if the {@link SupportedAppType} is installed. + * + * @param context + * @param supportedApp + * @return + */ + public boolean isInstalled(@NonNull Context context, SupportedAppType supportedApp) { + return getInstalledUberAppPackage(context, Collections.singletonList(supportedApp)) != null; + } + + @Nullable + public PackageInfo getInstalledUberAppPackage(@NonNull Context context) { + Pair installedApp = getInstalledUberApp(context); + return installedApp != null ? installedApp.second : null; + } + + @Nullable + public Pair getInstalledUberApp(@NonNull Context context) { + return getInstalledUberAppPackage(context, Arrays.asList(SupportedAppType.values())); } @Nullable - PackageInfo getInstalledUberAppPackage(@NonNull Context context) { - PackageInfo packageInfo = null; - for (String installedPackage : AppProtocol.UBER_PACKAGE_NAMES) { - if (PackageManagers.isPackageAvailable(context, installedPackage)) { - packageInfo = PackageManagers.getPackageInfo(context, installedPackage); - break; + private Pair getInstalledUberAppPackage( + @NonNull Context context, @NonNull Collection selectedApps) { + for (SupportedAppType app : selectedApps) { + for (String installedPackage : getSupportedPackageNames().get(app)) { + if (PackageManagers.isPackageAvailable(context, installedPackage)) { + return new Pair<>(app, PackageManagers.getPackageInfo(context, installedPackage)); + } } } - return packageInfo; + return null; } /** @@ -68,8 +129,7 @@ public boolean isAppLinkSupported() { /** * Validates the app signature required or returns true if in debug. */ - @SuppressLint("PackageManagerGetSignatures") - public boolean validateSignature(Context context, String packageName) { + public boolean validateSignature(@NonNull Context context, @NonNull String packageName) { if (isDebug(context)) { return true; } @@ -130,7 +190,7 @@ MessageDigest getSha1MessageDigest() throws NoSuchAlgorithmException { return MessageDigest.getInstance(HASH_ALGORITHM_SHA1); } - private boolean isDebug(Context context) { + private boolean isDebug(@NonNull Context context) { String brand = Build.BRAND; int applicationFlags = context.getApplicationInfo().flags; if ((brand.startsWith("Android") || brand.startsWith("generic")) && diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenManagerTest.java index ff46216b..074a3b8f 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenManagerTest.java @@ -33,9 +33,11 @@ import org.junit.Test; import org.robolectric.RuntimeEnvironment; +import javax.annotation.Nullable; import java.util.Set; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -266,7 +268,8 @@ public void setAccessToken_whenBothDefaultAndCustomTokenSet_shouldSucceed() thro assertAccessTokensEqual(ACCESS_TOKEN_SECOND, tokenPreferences.getAccessToken(CUSTOM_ACCESS_TOKEN_KEY)); } - private void assertAccessTokensEqual(AccessToken accessTokenExpected, AccessToken accessTokenActual) { + private void assertAccessTokensEqual(AccessToken accessTokenExpected, @Nullable AccessToken accessTokenActual) { + assertNotNull(accessTokenActual); assertEquals(accessTokenExpected.getExpiresIn(), accessTokenActual.getExpiresIn()); assertEquals(accessTokenExpected.getToken(), accessTokenActual.getToken()); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 0bf72171..13b1d59a 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -138,7 +138,7 @@ public void setup() { @Test public void login_withLegacyModeBlocking_shouldNotLogin() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(false); loginManager.login(activity); @@ -147,7 +147,7 @@ public void login_withLegacyModeBlocking_shouldNotLogin() { @Test public void login_withLegacyModeNotBlocking_shouldLogin() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(true); loginManager.login(activity); @@ -156,7 +156,7 @@ public void login_withLegacyModeNotBlocking_shouldLogin() { @Test public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); loginManager.login(activity); @@ -174,7 +174,7 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_VERSION_SUPPORTED); + stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); loginManager.login(activity); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 60cb8137..9c32b718 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -26,15 +26,14 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.net.Uri; - +import android.util.Pair; import com.google.common.collect.Sets; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.Scope; - import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -45,9 +44,10 @@ import java.util.Collection; import java.util.Set; +import static com.uber.sdk.android.core.SupportedAppType.UBER; +import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; +import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; @@ -67,42 +67,51 @@ public class SsoDeeplinkTest extends RobolectricTestBase { + BuildConfig.VERSION_NAME; @Mock - PackageManager packageManager; - - @Mock - AppProtocol protocol; + AppProtocol appProtocol; Activity activity; @Before - public void setUp() throws Exception { + public void setUp() { activity = spy(Robolectric.setupActivity(Activity.class)); } @Test public void testIsSupported_appInstalled_shouldBeTrue() { - enableSupport(); + enableSupport(UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .build(); + + assertThat(link.isSupported()).isTrue(); + } + + @Test + public void testIsSupported_eatsAppInstalled_shouldBeTrue() { + enableSupport(UBER_EATS, SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED); + + final SsoDeeplink link = new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) .build(); - link.appProtocol = protocol; - final boolean isSupported = link.isSupported(); - assertThat(isSupported).isTrue(); + assertThat(link.isSupported()).isTrue(); } @Test public void testIsSupported_appInstalledWithBadSignature_shouldBeFalse() { enableSupport(); - when(protocol.validateSignature(any(Context.class), anyString())).thenReturn(false); + when(appProtocol.validateSignature(any(Context.class), anyString())).thenReturn(false); final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) .build(); - link.appProtocol = protocol; final boolean isSupported = link.isSupported(); @@ -111,60 +120,46 @@ public void testIsSupported_appInstalledWithBadSignature_shouldBeFalse() { @Test public void testIsSupported_appInstalledButOldVersion_shouldBeFalse() { - final PackageInfo packageInfo = new PackageInfo(); - packageInfo.versionCode = SsoDeeplink.MIN_VERSION_SUPPORTED - 1; - - when(activity.getPackageManager()).thenReturn(packageManager); - try { - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], 0)).thenReturn(packageInfo); - } catch (PackageManager.NameNotFoundException e) { - fail("Unable to mock Package Manager"); - } - - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + PackageInfo packageInfo = new PackageInfo(); + packageInfo.versionCode = 0; + when(appProtocol.getInstalledUberAppPackage(activity)).thenReturn(packageInfo); + when(appProtocol.getInstalledUberApp(activity)).thenReturn(Pair.create(UBER, packageInfo)); + when(appProtocol.validateSignature(any(Context.class), anyString())).thenReturn(true); + when(appProtocol.validateMinimumVersion(eq(activity), any(PackageInfo.class), eq(0))).thenReturn(false); + + SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) .build(); - link.appProtocol = protocol; - final boolean isSupported = link.isSupported(); - - assertThat(isSupported).isFalse(); + assertThat(link.isSupported()).isFalse(); } @Test public void testIsSupported_noAppInstalled_shouldBeFalse() { - when(activity.getPackageManager()).thenReturn(packageManager); - try { - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_META_DATA)) - .thenThrow(PackageManager.NameNotFoundException.class); - } catch (PackageManager.NameNotFoundException e) { - fail("Unable to mock Package Manager"); - } + when(appProtocol.getInstalledUberApp(activity)).thenReturn(null); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) .build(); - link.appProtocol = protocol; - final boolean isSupported = link.isSupported(); - - assertThat(isSupported).isFalse(); + assertThat(link.isSupported()).isFalse(); } @Test public void testInvokeWithoutRegion_shouldUseWorld() { enableSupport(); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(Scope.HISTORY, Scope.PROFILE) .activityRequestCode(REQUEST_CODE) - .build(); - - link.appProtocol = protocol; - link.execute(); + .appProtocol(appProtocol) + .build() + .execute(); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -180,13 +175,12 @@ public void testInvokeWithoutRegion_shouldUseWorld() { public void testInvokeWithoutRequestCode_shouldUseDefaultRequstCode() { enableSupport(); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) - .build(); - - link.appProtocol = protocol; - link.execute(); + .appProtocol(appProtocol) + .build() + .execute(); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); @@ -203,14 +197,12 @@ public void testInvokeWithoutRequestCode_shouldUseDefaultRequstCode() { public void testInvokeWithoutScopes_shouldFail() { enableSupport(); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .activityRequestCode(REQUEST_CODE) - .build(); - - link.appProtocol = protocol; - link.execute(); - + .appProtocol(appProtocol) + .build() + .execute(); } @Test @@ -219,15 +211,15 @@ public void testInvokeWithScopesAndCustomScopes_shouldSucceed() { Collection collection = Arrays.asList("sample", "test"); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) .customScopes(collection) - .build(); + .appProtocol(appProtocol) + .build() + .execute(); - link.appProtocol = protocol; - link.execute(); ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); @@ -239,24 +231,17 @@ public void testInvokeWithScopesAndCustomScopes_shouldSucceed() { public void testInvokeWithoutClientId_shouldFail() { enableSupport(); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + new SsoDeeplink.Builder(activity) .scopes(GENERAL_SCOPES) .activityRequestCode(REQUEST_CODE) - .build(); - - link.appProtocol = protocol; - link.execute(); + .appProtocol(appProtocol) + .build() + .execute(); } @Test(expected = IllegalStateException.class) public void testInvokeWithoutAppInstalled_shouldFail() { - when(activity.getPackageManager()).thenReturn(packageManager); - try { - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_META_DATA)) - .thenThrow(PackageManager.NameNotFoundException.class); - } catch (PackageManager.NameNotFoundException e) { - fail("Unable to mock Package Manager"); - } + when(appProtocol.getInstalledUberApp(activity)).thenReturn(null); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) @@ -266,18 +251,15 @@ public void testInvokeWithoutAppInstalled_shouldFail() { } private void enableSupport() { - final PackageInfo packageInfo = new PackageInfo(); - packageInfo.versionCode = SsoDeeplink.MIN_VERSION_SUPPORTED; - - when(activity.getPackageManager()).thenReturn(packageManager); - - try { - when(packageManager.getPackageInfo(eq(AppProtocol.UBER_PACKAGE_NAMES[0]), anyInt())) - .thenReturn(packageInfo); - } catch (PackageManager.NameNotFoundException e) { - fail("Unable to mock Package Manager"); - } - when(protocol.validateSignature(any(Context.class), anyString())).thenReturn(true); - when(protocol.validateMinimumVersion(any(Context.class), any(PackageInfo.class), anyInt())).thenReturn(true); + enableSupport(UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + } + + private void enableSupport(SupportedAppType appType, int versionCode) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.versionCode = versionCode; + when(appProtocol.getInstalledUberAppPackage(activity)).thenReturn(packageInfo); + when(appProtocol.getInstalledUberApp(activity)).thenReturn(Pair.create(appType, packageInfo)); + when(appProtocol.validateSignature(any(Context.class), anyString())).thenReturn(true); + when(appProtocol.validateMinimumVersion(eq(activity), any(PackageInfo.class), eq(versionCode))).thenReturn(true); } -} \ No newline at end of file +} diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java index af720c80..393111c4 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java @@ -34,6 +34,7 @@ import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; +import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; @@ -76,7 +77,7 @@ public void execute() { @Override public boolean isSupported() { - return appProtocol.isUberInstalled(context); + return appProtocol.isInstalled(context, UBER); } /** @@ -226,7 +227,7 @@ private void addLocation( Uri.Builder getUriBuilder(@NonNull Context context, @NonNull Deeplink.Fallback fallback) { final Uri.Builder builder; - if (appProtocol.isUberInstalled(context)) { + if (appProtocol.isInstalled(context, UBER)) { if (appProtocol.isAppLinkSupported()) { builder = Uri.parse(Deeplink.APP_LINK_URI).buildUpon(); } else { diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java index 4c944b3a..358dc2b4 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java @@ -22,19 +22,13 @@ package com.uber.sdk.android.rides; -import android.app.Activity; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.net.Uri; import android.support.customtabs.CustomTabsIntent; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.android.core.utils.CustomTabsHelper; -import com.uber.sdk.android.core.utils.PackageManagers; import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; @@ -42,13 +36,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.res.builder.RobolectricPackageManager; -import org.robolectric.shadows.ShadowActivity; import java.io.IOException; +import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.rides.TestUtils.readUriResourceWithUserAgentParam; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -56,15 +47,12 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.robolectric.Shadows.shadowOf; /** * Tests {@link RideRequestDeeplink} */ public class RideRequestDeeplinkTest extends RobolectricTestBase { - 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 Double PICKUP_LAT = 32.1234; private static final Double PICKUP_LONG = -122.3456; @@ -84,7 +72,7 @@ public class RideRequestDeeplinkTest extends RobolectricTestBase { @Before public void setup() { MockitoAnnotations.initMocks(this); - when(appProtocol.isUberInstalled(eq(context))).thenReturn(true); + when(appProtocol.isInstalled(eq(context), eq(UBER))).thenReturn(true); when(appProtocol.isAppLinkSupported()).thenReturn(false); } @@ -190,7 +178,6 @@ public void getUri_whenUberAppInstalledAndAppLinkSupported_shouldUseAppLink() th ("src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided", USER_AGENT_DEEPLINK); - when(appProtocol.isUberInstalled(eq(context))).thenReturn(true); when(appProtocol.isAppLinkSupported()).thenReturn(true); RideParameters rideParameters = new RideParameters.Builder().build(); @@ -210,7 +197,6 @@ public void getUri_whenUberAppInstalledAndAppLinkNotSupported_shouldUseNativeLin ("src/test/resources/deeplinkuris/just_client_provided", USER_AGENT_DEEPLINK); - when(appProtocol.isUberInstalled(eq(context))).thenReturn(true); when(appProtocol.isAppLinkSupported()).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); @@ -229,7 +215,7 @@ public void getUri_whenUberAppNotInstalledAndFallbackMobileWeb_shouldUseMobileWe ("src/test/resources/deeplinkuris/mobile_web_just_client_provided", USER_AGENT_DEEPLINK); - when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); + when(appProtocol.isInstalled(eq(context), eq(UBER))).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) @@ -248,7 +234,7 @@ public void getUri_whenUberAppNotInstalledAndFallbackAppInstall_shouldUseAppInst ("src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided", USER_AGENT_DEEPLINK); - when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); + when(appProtocol.isInstalled(eq(context), eq(UBER))).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) @@ -267,7 +253,7 @@ public void getUri_whenUberAppNotInstalledAndFallbackNotSet_shouldUseAppInstall( ("src/test/resources/deeplinkuris/mobile_web_ul_just_client_provided", USER_AGENT_DEEPLINK); - when(appProtocol.isUberInstalled(eq(context))).thenReturn(false); + when(appProtocol.isInstalled(eq(context), eq(UBER))).thenReturn(false); RideParameters rideParameters = new RideParameters.Builder().build(); RideRequestDeeplink rideRequestDeeplink = new RideRequestDeeplink.Builder(context) From caa3046f7e4c91478a9e4d22243391f365d78039 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Thu, 16 Aug 2018 16:17:40 -0400 Subject: [PATCH 084/165] Refactored AppProtocol to include signature and min-version validation in most public APIs. --- .../sdk/android/core/auth/SsoDeeplink.java | 34 +++---- .../sdk/android/core/utils/AppProtocol.java | 90 +++++++++++------- .../android/core/auth/SsoDeeplinkTest.java | 92 ++++++++----------- 3 files changed, 108 insertions(+), 108 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 9ad955d2..b6a85736 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -39,6 +39,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.List; import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; @@ -103,9 +105,12 @@ public void execute() { final Uri deepLinkUri = createSsoUri(); intent.setData(deepLinkUri); - PackageInfo installedPackage = appProtocol.getInstalledUberAppPackage(activity); - if (installedPackage != null) { - intent.setPackage(installedPackage.packageName); + List validatedPackages = new ArrayList<>(); + validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)); + validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)); + + if(!validatedPackages.isEmpty()) { + intent.setPackage(validatedPackages.get(0).packageName); } activity.startActivityForResult(intent, requestCode); } @@ -128,29 +133,12 @@ private Uri createSsoUri() { /** * Check if SSO deep linking is supported in this device. * - * @return + * @return true if package name and minimum version conditions are met. */ @Override public boolean isSupported() { - Pair installedUberAppPackage = appProtocol.getInstalledUberApp(activity); - if (installedUberAppPackage == null) { - return false; - } - - SupportedAppType supportedApp = installedUberAppPackage.first; - int minimumVersion; - if (UBER == supportedApp) { - minimumVersion = MIN_UBER_RIDES_VERSION_SUPPORTED; - } else if (UBER_EATS == supportedApp) { - minimumVersion = MIN_UBER_EATS_VERSION_SUPPORTED; - } else { - return false; - } - - PackageInfo packageInfo = installedUberAppPackage.second; - return packageInfo != null - && appProtocol.validateMinimumVersion(activity, packageInfo, minimumVersion) - && appProtocol.validateSignature(activity, packageInfo.packageName); + return appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED) + || appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); } public static class Builder { diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 46e37303..5f81279d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -15,9 +15,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -42,6 +41,7 @@ public class AppProtocol { private static final String UBER_RIDER_HASH = "411c40b31f6d01dac68d711df99b6eafeec8e73b"; private static final String UBER_EATS_HASH = "ae0b86995f174533b423067837beba13d922fbb0"; private static final String HASH_ALGORITHM_SHA1 = "SHA-1"; + private static final int DEFAULT_MIN_VERSION = 0; private static final HashSet validAppSignatureHashes = buildAppSignatureHashes(); @@ -85,38 +85,69 @@ public boolean isUberInstalled(@NonNull Context context) { } /** - * Check if the {@link SupportedAppType} is installed. + * Verify if any version of the app has been installed on this device. * - * @param context - * @param supportedApp - * @return + * @param context A {@link Context}. + * @param supportedApp The {@link SupportedAppType}. + * @return {@code true} if any version of the app is installed (min version of 0). */ - public boolean isInstalled(@NonNull Context context, SupportedAppType supportedApp) { - return getInstalledUberAppPackage(context, Collections.singletonList(supportedApp)) != null; + public boolean isInstalled(@NonNull Context context, @NonNull SupportedAppType supportedApp) { + return isInstalled(context, supportedApp, DEFAULT_MIN_VERSION); } - @Nullable - public PackageInfo getInstalledUberAppPackage(@NonNull Context context) { - Pair installedApp = getInstalledUberApp(context); - return installedApp != null ? installedApp.second : null; + /** + * Check if the minimum version of {@link SupportedAppType} is installed. + * + * @param context A {@link Context}. + * @param supportedApp The {@link SupportedAppType}. + * @param minimumVersion The minimum version of {@link SupportedAppType} that must be installed. + * @return {@code true} if any valid package for the app is installed. + */ + public boolean isInstalled(@NonNull Context context, @NonNull SupportedAppType supportedApp, int minimumVersion) { + return !getInstalledPackages(context, supportedApp, minimumVersion).isEmpty(); + } - @Nullable - public Pair getInstalledUberApp(@NonNull Context context) { - return getInstalledUberAppPackage(context, Arrays.asList(SupportedAppType.values())); + /** + * Find the installed and validated packages for a {@link SupportedAppType}. + *

+ * This will validate the signature and minimum version of the installed package or exclude it from returned list. + * + * @param context A {@link Context}. + * @param supportedApp The {@link SupportedAppType}. + * @param minimumVersion The minimum version of {@link SupportedAppType} that must be installed. + * @return A list of {@link PackageInfo} which is installed and has been validated. + */ + @NonNull + public List getInstalledPackages( + @NonNull Context context, @NonNull SupportedAppType supportedApp, int minimumVersion) { + List packageInfos = new ArrayList<>(); + + List> installedApps = getInstalledPackagesByApp(context, supportedApp); + + for (Pair installedApp : installedApps) { + PackageInfo packageInfo = installedApp.second; + if (packageInfo != null + && validateSignature(context, packageInfo.packageName) + && validateMinimumVersion(context, packageInfo, minimumVersion)) { + packageInfos.add(packageInfo); + } + } + return packageInfos; } - @Nullable - private Pair getInstalledUberAppPackage( - @NonNull Context context, @NonNull Collection selectedApps) { - for (SupportedAppType app : selectedApps) { - for (String installedPackage : getSupportedPackageNames().get(app)) { - if (PackageManagers.isPackageAvailable(context, installedPackage)) { - return new Pair<>(app, PackageManagers.getPackageInfo(context, installedPackage)); - } + @NonNull + private List> getInstalledPackagesByApp( + @NonNull Context context, @NonNull SupportedAppType selectedApp) { + List> installedPackages = new ArrayList<>(); + + for (String installedPackage : getSupportedPackageNames().get(selectedApp)) { + if (PackageManagers.isPackageAvailable(context, installedPackage)) { + installedPackages.add( + Pair.create(selectedApp, PackageManagers.getPackageInfo(context, installedPackage))); } } - return null; + return installedPackages; } /** @@ -183,7 +214,7 @@ public String getAppSignature(@NonNull Context context) { * Gets an instance of {@link MessageDigest} with the SHA-1 Algorithm. * * @return the message digest. - * @throws NoSuchAlgorithmException + * @throws NoSuchAlgorithmException thrown by {@link MessageDigest#getInstance(String)}. */ @NonNull MessageDigest getSha1MessageDigest() throws NoSuchAlgorithmException { @@ -193,11 +224,8 @@ MessageDigest getSha1MessageDigest() throws NoSuchAlgorithmException { private boolean isDebug(@NonNull Context context) { String brand = Build.BRAND; int applicationFlags = context.getApplicationInfo().flags; - if ((brand.startsWith("Android") || brand.startsWith("generic")) && - (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - // We are debugging on an emulator, don't validate package signature. - return true; - } - return false; + // We are debugging on an emulator, don't validate package signature. + return (brand.startsWith("Android") || brand.startsWith("generic")) && + (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 9c32b718..4103c6c0 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -23,15 +23,12 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.net.Uri; -import android.util.Pair; import com.google.common.collect.Sets; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; -import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.Scope; import org.junit.Before; @@ -42,15 +39,15 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Set; import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; +import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED; import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -77,8 +74,9 @@ public void setUp() { } @Test - public void testIsSupported_appInstalled_shouldBeTrue() { - enableSupport(UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + public void isSupported_appInstalled_shouldBeTrue() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) @@ -90,8 +88,9 @@ public void testIsSupported_appInstalled_shouldBeTrue() { } @Test - public void testIsSupported_eatsAppInstalled_shouldBeTrue() { - enableSupport(UBER_EATS, SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED); + public void isSupported_eatsAppInstalled_shouldBeTrue() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) @@ -103,9 +102,9 @@ public void testIsSupported_eatsAppInstalled_shouldBeTrue() { } @Test - public void testIsSupported_appInstalledWithBadSignature_shouldBeFalse() { - enableSupport(); - when(appProtocol.validateSignature(any(Context.class), anyString())).thenReturn(false); + public void isSupported_appNotInstalled_shouldBeFalse() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) @@ -113,44 +112,36 @@ public void testIsSupported_appInstalledWithBadSignature_shouldBeFalse() { .appProtocol(appProtocol) .build(); - final boolean isSupported = link.isSupported(); - - assertThat(isSupported).isFalse(); + assertThat(link.isSupported()).isFalse(); } @Test - public void testIsSupported_appInstalledButOldVersion_shouldBeFalse() { + public void execute_withInstalledPackage_shouldSetPackage() { + String packageName = "PACKAGE_NAME"; PackageInfo packageInfo = new PackageInfo(); - packageInfo.versionCode = 0; - when(appProtocol.getInstalledUberAppPackage(activity)).thenReturn(packageInfo); - when(appProtocol.getInstalledUberApp(activity)).thenReturn(Pair.create(UBER, packageInfo)); - when(appProtocol.validateSignature(any(Context.class), anyString())).thenReturn(true); - when(appProtocol.validateMinimumVersion(eq(activity), any(PackageInfo.class), eq(0))).thenReturn(false); + packageInfo.packageName = packageName; - SsoDeeplink link = new SsoDeeplink.Builder(activity) - .clientId(CLIENT_ID) - .scopes(GENERAL_SCOPES) - .appProtocol(appProtocol) - .build(); + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); + when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)) + .thenReturn(Collections.singletonList(packageInfo)); - assertThat(link.isSupported()).isFalse(); - } - - @Test - public void testIsSupported_noAppInstalled_shouldBeFalse() { - when(appProtocol.getInstalledUberApp(activity)).thenReturn(null); - - SsoDeeplink link = new SsoDeeplink.Builder(activity) + new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) - .scopes(GENERAL_SCOPES) + .scopes(Scope.HISTORY, Scope.PROFILE) + .activityRequestCode(REQUEST_CODE) .appProtocol(appProtocol) - .build(); + .build() + .execute(); - assertThat(link.isSupported()).isFalse(); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE)); + Intent intent = intentCaptor.getValue(); + + assertThat(intent.getPackage()).isEqualTo(packageName); } @Test - public void testInvokeWithoutRegion_shouldUseWorld() { + public void execute_withoutRegion_shouldUseWorld() { enableSupport(); new SsoDeeplink.Builder(activity) @@ -172,7 +163,7 @@ public void testInvokeWithoutRegion_shouldUseWorld() { } @Test - public void testInvokeWithoutRequestCode_shouldUseDefaultRequstCode() { + public void execute_withoutRequestCode_shouldUseDefaultRequstCode() { enableSupport(); new SsoDeeplink.Builder(activity) @@ -194,7 +185,7 @@ public void testInvokeWithoutRequestCode_shouldUseDefaultRequstCode() { @Test(expected = IllegalStateException.class) - public void testInvokeWithoutScopes_shouldFail() { + public void execute_withoutScopes_shouldFail() { enableSupport(); new SsoDeeplink.Builder(activity) @@ -206,7 +197,7 @@ public void testInvokeWithoutScopes_shouldFail() { } @Test - public void testInvokeWithScopesAndCustomScopes_shouldSucceed() { + public void execute_withScopesAndCustomScopes_shouldSucceed() { enableSupport(); Collection collection = Arrays.asList("sample", "test"); @@ -228,7 +219,7 @@ public void testInvokeWithScopesAndCustomScopes_shouldSucceed() { } @Test(expected = NullPointerException.class) - public void testInvokeWithoutClientId_shouldFail() { + public void execute_withoutClientId_shouldFail() { enableSupport(); new SsoDeeplink.Builder(activity) @@ -240,8 +231,9 @@ public void testInvokeWithoutClientId_shouldFail() { } @Test(expected = IllegalStateException.class) - public void testInvokeWithoutAppInstalled_shouldFail() { - when(appProtocol.getInstalledUberApp(activity)).thenReturn(null); + public void execute_withoutAppInstalled_shouldFail() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) @@ -251,15 +243,7 @@ public void testInvokeWithoutAppInstalled_shouldFail() { } private void enableSupport() { - enableSupport(UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); - } - - private void enableSupport(SupportedAppType appType, int versionCode) { - PackageInfo packageInfo = new PackageInfo(); - packageInfo.versionCode = versionCode; - when(appProtocol.getInstalledUberAppPackage(activity)).thenReturn(packageInfo); - when(appProtocol.getInstalledUberApp(activity)).thenReturn(Pair.create(appType, packageInfo)); - when(appProtocol.validateSignature(any(Context.class), anyString())).thenReturn(true); - when(appProtocol.validateMinimumVersion(eq(activity), any(PackageInfo.class), eq(versionCode))).thenReturn(true); + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); } } From 120ad54d9758fe14331da6c0771da953ed39ef5a Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Thu, 16 Aug 2018 16:30:27 -0400 Subject: [PATCH 085/165] Cleanup AppProtocolTest --- .../android/core/utils/AppProtocolTest.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java b/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java index 508ace04..6cb0bb8c 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/utils/AppProtocolTest.java @@ -4,9 +4,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; - import com.uber.sdk.android.core.RobolectricTestBase; - import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -61,35 +59,35 @@ public class AppProtocolTest extends RobolectricTestBase { @Mock PackageInfo packageInfo; - Activity activity; - AppProtocol appProtocol; + private Activity activity; + private AppProtocol appProtocol; @Before - public void setUp() throws Exception { + public void setUp() { activity = spy(Robolectric.setupActivity(Activity.class)); appProtocol = new AppProtocol(); } @Test - public void validateSignature_whenValid_returnsTrue() throws Exception { + public void validateSignature_whenValid_returnsTrue() { stubAppSignature(GOOD_SIGNATURE); - assertTrue(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAMES[0])); + assertTrue(appProtocol.validateSignature(activity, AppProtocol.RIDER_PACKAGE_NAMES[0])); } @Test - public void validateSignature_whenInvalid_returnsFalse() throws Exception { + public void validateSignature_whenInvalid_returnsFalse() { stubAppSignature(BAD_SIGNATURE); - assertFalse(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAMES[0])); + assertFalse(appProtocol.validateSignature(activity, AppProtocol.RIDER_PACKAGE_NAMES[0])); } @Test - public void validateSignature_whenGoodAndBad_returnsFalse() throws Exception { + public void validateSignature_whenGoodAndBad_returnsFalse() { stubAppSignature(GOOD_SIGNATURE, BAD_SIGNATURE); - assertFalse(appProtocol.validateSignature(activity, AppProtocol.UBER_PACKAGE_NAMES[0])); + assertFalse(appProtocol.validateSignature(activity, AppProtocol.RIDER_PACKAGE_NAMES[0])); } @Test - public void getApplicationSignature_whenValidPackageSignature_shouldSucceed() throws Exception { + public void getApplicationSignature_whenValidPackageSignature_shouldSucceed() { stubAppSignature(GOOD_SIGNATURE); assertThat(appProtocol.getAppSignature(activity)).isEqualTo(GOOD_HASH); } @@ -99,7 +97,7 @@ public void getPackageSignature_whenNameNotFoundException_shouldReturnNull() thr stubAppSignature(GOOD_SIGNATURE); final Throwable throwable = new PackageManager.NameNotFoundException(); doThrow(throwable).when(packageManager) - .getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_SIGNATURES); + .getPackageInfo(AppProtocol.RIDER_PACKAGE_NAMES[0], PackageManager.GET_SIGNATURES); assertThat(appProtocol.getAppSignature(activity)).isNull(); } @@ -107,16 +105,16 @@ public void getPackageSignature_whenNameNotFoundException_shouldReturnNull() thr @Test public void getPackageSignature_whenNullPackageInfo_shouldReturnNull() throws Exception { stubAppSignature(GOOD_SIGNATURE); - when(packageManager.getPackageInfo(AppProtocol.UBER_PACKAGE_NAMES[0], PackageManager.GET_SIGNATURES)) + when(packageManager.getPackageInfo(AppProtocol.RIDER_PACKAGE_NAMES[0], PackageManager.GET_SIGNATURES)) .thenReturn(null); assertThat(appProtocol.getAppSignature(activity)).isNull(); } @Test - public void getPackageSignature_whenEmptyPackageInfo_shouldReturnNull() throws Exception { + public void getPackageSignature_whenEmptyPackageInfo_shouldReturnNull() { stubAppSignature(GOOD_SIGNATURE); - packageInfo.signatures = new Signature[] {}; + packageInfo.signatures = new Signature[]{}; assertThat(appProtocol.getAppSignature(activity)).isNull(); } @@ -132,8 +130,8 @@ public void getPackageSignature_whenNoSuchAlgorithmException_shouldReturnNull() assertThat(appProtocol.getAppSignature(activity)).isNull(); } - private void stubAppSignature(String... sig) throws Exception { - when(activity.getPackageName()).thenReturn(AppProtocol.UBER_PACKAGE_NAMES[0]); + private void stubAppSignature(String... sig) { + when(activity.getPackageName()).thenReturn(AppProtocol.RIDER_PACKAGE_NAMES[0]); when(activity.getPackageManager()).thenReturn(packageManager); Signature[] signatures = new Signature[sig.length]; @@ -144,7 +142,7 @@ private void stubAppSignature(String... sig) throws Exception { packageInfo.signatures = signatures; try { - when(packageManager.getPackageInfo(eq(AppProtocol.UBER_PACKAGE_NAMES[0]), anyInt())) + when(packageManager.getPackageInfo(eq(AppProtocol.RIDER_PACKAGE_NAMES[0]), anyInt())) .thenReturn(packageInfo); } catch (PackageManager.NameNotFoundException e) { fail("Unable to mock Package Manager"); From c3ba4663f188dd9d36602f75f02bbff4e39e8eb1 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Tue, 9 Oct 2018 16:38:05 -0400 Subject: [PATCH 086/165] Add support for prioritizing SSO Apps --- README.md | 16 +++++- .../sdk/android/core/auth/LoginManager.java | 36 +++++++++---- .../sdk/android/core/auth/SsoDeeplink.java | 36 ++++++++++--- .../android/core/auth/LoginManagerTest.java | 48 +++++++++++++++-- .../android/core/auth/SsoDeeplinkTest.java | 54 +++++++++++++++++++ 5 files changed, 169 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 7bfc6ec9..6209f33a 100644 --- a/README.md +++ b/README.md @@ -291,11 +291,10 @@ loginManager.setAuthCodeFlowEnabled(true); loginManager.login(this); ``` - Once the code is exchanged, the server should redirect to a URI in the standard OAUTH format of `com.example.app.uberauth://redirect#access_token=ACCESS_TOKEN&token_type=Bearer&expires_in=TTL&scope=SCOPES&refresh_token=REFRESH_TOKEN` for the SDK to receive the access token and continue operation.`` - + ##### Authorization Code Flow @@ -306,6 +305,19 @@ otherwise redirect to the Play Store. If you require Authorization Code Grant, s to use the Authorization Code Flow as the fallback mechanism instead of Implicit Grant or redirecting to the Play Store (regardless of scope). Implicit Grant will allow access to all non-privileged scopes (and will not grant a refresh token), whereas the other options grant access to privileged scopes. [Read more about scopes](https://developer.uber.com/docs/scopes). +##### SSO Product Priority + +The default behavior of the SSO Deeplink is to open the original Uber app. It is now possible to SSO with the Uber Eats app. To enable SSO with Uber Eats use the LoginManager's `setProductFlowPriority` method. +You must specify all apps that you want to SSO with. Only the specified apps will be used. + +```java +List appPriorityList = new ArrayList(); +appPriorityList.add(SupportedAppType.UBER_EATS); +appPriorityList.add(SupportedAppType.UBER); + +loginManager.setProductFlowPriority(appPriorityList).login(this); +``` + #### Login Errors Upon a failure to login, an `AuthenticationError` will be provided in the `LoginCallback`. This enum provides a series of values that provide more information on the type of error. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 7d7ba740..02222e84 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -27,8 +27,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; - import com.uber.sdk.android.core.BuildConfig; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; @@ -40,6 +40,8 @@ import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; +import java.util.Collection; + import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; @@ -96,6 +98,7 @@ public class LoginManager { private final int requestCode; private final LegacyUriRedirectHandler legacyUriRedirectHandler; + private Collection productFlowPriority; private boolean authCodeFlowEnabled = false; @Deprecated private boolean redirectForAuthorizationCode = false; @@ -138,11 +141,11 @@ public LoginManager( } /** - * @param accessTokenStorage to store access token. - * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. - * @param configuration to provide authentication information - * @param requestCode custom code to use for Activity communication - * @param legacyUriRedirectHandler Used to handle URI Redirect Migration + * @param accessTokenStorage to store access token. + * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} is called. + * @param configuration to provide authentication information + * @param requestCode custom code to use for Activity communication + * @param legacyUriRedirectHandler Used to handle URI Redirect Migration */ LoginManager( @NonNull AccessTokenStorage accessTokenStorage, @@ -176,6 +179,7 @@ public void login(final @NonNull Activity activity) { .clientId(sessionConfiguration.getClientId()) .scopes(sessionConfiguration.getScopes()) .customScopes(sessionConfiguration.getCustomScopes()) + .productFlowPriority(productFlowPriority) .activityRequestCode(requestCode) .build(); @@ -322,19 +326,33 @@ public boolean isRedirectForAuthorizationCode() { * (See * https://developer.uber.com/docs/authentication#section-step-one-authorize) instead of an * installation prompt for the Uber app or Implicit Grant (WebView) as a login fallback mechanism. - * + *

* Requires that the app's backend system is configured to support this flow and the redirect * URI is pointed correctly. * * @param authCodeFlowEnabled true for use of auth code flow, false to fallback to Uber app - * installation - * @return this instace of {@link LoginManager} + * installation + * @return this instance of {@link LoginManager}. */ public LoginManager setAuthCodeFlowEnabled(boolean authCodeFlowEnabled) { this.authCodeFlowEnabled = authCodeFlowEnabled; return this; } + /** + * Dictates the order of which Uber applications should be used for SSO. + * This can be used to order Eats then Rides or vice-versa. + * Only specified applications will be used, so specifying only Rides or Eats will ignore other apps if installed. + * The default behavior (for backward compatibility) is Rides only. + * + * @param productFlowPriority A Collection of SupportedAppType indicating priority of SSO applications. + * @return this instance of {@link LoginManager}. + */ + public LoginManager setProductFlowPriority(@NonNull Collection productFlowPriority) { + this.productFlowPriority = productFlowPriority; + return this; + } + /** * Indicates the use of the Authorization Code Flow * (See diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index b6a85736..d1ad81cf 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -29,7 +29,6 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.Log; -import android.util.Pair; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.SupportedAppType; @@ -39,7 +38,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import static com.uber.sdk.android.core.SupportedAppType.UBER; @@ -73,6 +71,7 @@ public class SsoDeeplink implements Deeplink { private final String clientId; private final Collection requestedScopes; private final Collection requestedCustomScopes; + private final Collection productFlowPriority; private final int requestCode; private SsoDeeplink( @@ -81,6 +80,7 @@ private SsoDeeplink( @NonNull String clientId, @NonNull Collection requestedScopes, @NonNull Collection requestedCustomScopes, + @NonNull Collection productFlowPriority, int requestCode) { this.activity = activity; this.appProtocol = appProtocol; @@ -88,6 +88,7 @@ private SsoDeeplink( this.requestCode = requestCode; this.requestedScopes = requestedScopes; this.requestedCustomScopes = requestedCustomScopes; + this.productFlowPriority = productFlowPriority; } /** @@ -106,10 +107,22 @@ public void execute() { intent.setData(deepLinkUri); List validatedPackages = new ArrayList<>(); - validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)); - validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)); + if (productFlowPriority.isEmpty()) { + validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)); + } else { + for (SupportedAppType supportedAppType : productFlowPriority) { + switch (supportedAppType) { + case UBER: + validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)); + break; + case UBER_EATS: + validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)); + break; + } + } + } - if(!validatedPackages.isEmpty()) { + if (!validatedPackages.isEmpty()) { intent.setPackage(validatedPackages.get(0).packageName); } activity.startActivityForResult(intent, requestCode); @@ -138,7 +151,7 @@ private Uri createSsoUri() { @Override public boolean isSupported() { return appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED) - || appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); + || appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); } public static class Builder { @@ -148,6 +161,7 @@ public static class Builder { private String clientId; private Collection requestedScopes; private Collection requestedCustomScopes; + private Collection productFlowPriority; private int requestCode = DEFAULT_REQUEST_CODE; public Builder(@NonNull Activity activity) { @@ -174,6 +188,11 @@ public Builder customScopes(@NonNull Collection customScopes) { return this; } + public Builder productFlowPriority(@NonNull Collection productFlowPriority) { + this.productFlowPriority = productFlowPriority; + return this; + } + public Builder activityRequestCode(int requestCode) { this.requestCode = requestCode; return this; @@ -202,11 +221,16 @@ public SsoDeeplink build() { appProtocol = new AppProtocol(); } + if (productFlowPriority == null) { + productFlowPriority = new ArrayList<>(); + } + return new SsoDeeplink(activity, appProtocol, clientId, requestedScopes, requestedCustomScopes, + productFlowPriority, requestCode); } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 13b1d59a..d558ab37 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -28,10 +28,10 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; - import com.google.common.collect.ImmutableList; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenAuthenticator; @@ -39,13 +39,16 @@ import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; - import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.robolectric.Robolectric; +import java.util.List; + +import static com.uber.sdk.android.core.SupportedAppType.UBER; +import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ACCESS_TOKEN; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_CODE_RECEIVED; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ERROR; @@ -114,7 +117,8 @@ public class LoginManagerTest extends RobolectricTestBase { @Mock AccessTokenStorage accessTokenStorage; - @Mock LegacyUriRedirectHandler legacyUriRedirectHandler; + @Mock + LegacyUriRedirectHandler legacyUriRedirectHandler; SessionConfiguration sessionConfiguration; @@ -187,6 +191,42 @@ public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchInte assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); } + @Test + public void login_withEatsProductPriority_shouldLaunchEats() { + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); + stubAppInstalled(packageManager, "com.ubercab", SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); + stubAppInstalled(packageManager, "com.ubercab.eats", SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED); + List appTypes = ImmutableList.of(UBER_EATS); + + loginManager.setProductFlowPriority(appTypes).login(activity); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + + assertThat(intentCaptor.getValue().getPackage()).isEqualTo("com.ubercab.eats"); + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); + } + + @Test + public void login_withRidesAndEatsProductPriority_shouldLaunchRides() { + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); + stubAppInstalled(packageManager, "com.ubercab", SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); + stubAppInstalled(packageManager, "com.ubercab.eats", SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED); + List appTypes = ImmutableList.of(UBER, UBER_EATS); + + loginManager.setProductFlowPriority(appTypes).login(activity); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + + assertThat(intentCaptor.getValue().getPackage()).isEqualTo("com.ubercab"); + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); + } + @Test public void loginWithoutAppInstalledGeneralScopesAndAuthCodeFlowDisabled_shouldLaunchImplicitGrant() { sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); @@ -481,7 +521,7 @@ public void getSession_withServerToken_successful() { public void getSession_withAccessToken_successful() { when(accessTokenStorage.getAccessToken()).thenReturn(ACCESS_TOKEN); Session session = loginManager.getSession(); - assertEquals(ACCESS_TOKEN, ((AccessTokenAuthenticator)session.getAuthenticator()).getTokenStorage().getAccessToken()); + assertEquals(ACCESS_TOKEN, ((AccessTokenAuthenticator) session.getAuthenticator()).getTokenStorage().getAccessToken()); } @Test(expected = IllegalStateException.class) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 4103c6c0..5d946e8f 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -26,9 +26,11 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.net.Uri; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.Scope; import org.junit.Before; @@ -218,6 +220,58 @@ public void execute_withScopesAndCustomScopes_shouldSucceed() { assertThat(uri.getQueryParameter("scope")).contains("history", "profile", "sample", "test"); } + @Test + public void execute_withEatsProductFlowPriority_shouldLaunchEats() { + enableSupport(); + String eatsPackageName = "com.ubercab.eats"; + PackageInfo eatsPackageInfo = new PackageInfo(); + eatsPackageInfo.packageName = eatsPackageName; + when(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)) + .thenReturn(Collections.singletonList(eatsPackageInfo)); + + Collection supportedAppTypes = ImmutableList.of(UBER_EATS); + + new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .activityRequestCode(REQUEST_CODE) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .productFlowPriority(supportedAppTypes) + .build() + .execute(); + + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); + + assertThat(intentArgumentCaptor.getValue().getPackage()).isEqualTo(eatsPackageName); + } + + @Test + public void execute_withMissingProductFlowPriority_shouldLaunchRides() { + enableSupport(); + String ridesPackageName = "com.ubercab"; + PackageInfo eatsPackageInfo = new PackageInfo(); + eatsPackageInfo.packageName = ridesPackageName; + when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)) + .thenReturn(Collections.singletonList(eatsPackageInfo)); + + Collection supportedAppTypes = ImmutableList.of(); + + new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .activityRequestCode(REQUEST_CODE) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .productFlowPriority(supportedAppTypes) + .build() + .execute(); + + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); + + assertThat(intentArgumentCaptor.getValue().getPackage()).isEqualTo(ridesPackageName); + } + @Test(expected = NullPointerException.class) public void execute_withoutClientId_shouldFail() { enableSupport(); From 9d5d8db71cc12443081d6209bc809fcd327fe886 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Tue, 9 Oct 2018 17:15:58 -0400 Subject: [PATCH 087/165] Simplifying loop logic of SupportedAppType. --- .../sdk/android/core/auth/SsoDeeplink.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index d1ad81cf..9c232881 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -108,17 +108,12 @@ public void execute() { List validatedPackages = new ArrayList<>(); if (productFlowPriority.isEmpty()) { - validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)); + validatedPackages.addAll( + appProtocol.getInstalledPackages(activity, UBER, getSupportedAppVersion(UBER))); } else { for (SupportedAppType supportedAppType : productFlowPriority) { - switch (supportedAppType) { - case UBER: - validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)); - break; - case UBER_EATS: - validatedPackages.addAll(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)); - break; - } + validatedPackages.addAll(appProtocol.getInstalledPackages( + activity, supportedAppType, getSupportedAppVersion(supportedAppType))); } } @@ -150,8 +145,25 @@ private Uri createSsoUri() { */ @Override public boolean isSupported() { - return appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED) - || appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); + if (productFlowPriority.isEmpty()) { + return appProtocol.isInstalled(activity, UBER, getSupportedAppVersion(UBER)); + } else { + for (SupportedAppType supportedAppType : productFlowPriority) { + if (appProtocol.isInstalled(activity, supportedAppType, getSupportedAppVersion(supportedAppType))) { + return true; + } + } + return false; + } + } + + private static int getSupportedAppVersion(SupportedAppType supportedAppType) { + if (UBER == supportedAppType) { + return MIN_UBER_RIDES_VERSION_SUPPORTED; + } else if (UBER_EATS == supportedAppType) { + return MIN_UBER_EATS_VERSION_SUPPORTED; + } + return Integer.MAX_VALUE; } public static class Builder { From 86063cccab6a71b36118833874751191cd27513f Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Tue, 9 Oct 2018 17:45:01 -0400 Subject: [PATCH 088/165] Adding tests for updated isSupported logic. --- .../android/core/auth/SsoDeeplinkTest.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 5d946e8f..14fa25c3 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -90,7 +90,7 @@ public void isSupported_appInstalled_shouldBeTrue() { } @Test - public void isSupported_eatsAppInstalled_shouldBeTrue() { + public void isSupported_eatsAppInstalled_withoutProductPriority_shouldBeFalse() { when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); @@ -100,6 +100,21 @@ public void isSupported_eatsAppInstalled_shouldBeTrue() { .appProtocol(appProtocol) .build(); + assertThat(link.isSupported()).isFalse(); + } + + @Test + public void isSupported_eatsAppInstalled_withProductPriority_shouldBeTrue() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); + + final SsoDeeplink link = new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .productFlowPriority(ImmutableList.of(UBER_EATS)) + .build(); + assertThat(link.isSupported()).isTrue(); } @@ -229,14 +244,12 @@ public void execute_withEatsProductFlowPriority_shouldLaunchEats() { when(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)) .thenReturn(Collections.singletonList(eatsPackageInfo)); - Collection supportedAppTypes = ImmutableList.of(UBER_EATS); - new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) - .productFlowPriority(supportedAppTypes) + .productFlowPriority(ImmutableList.of(UBER_EATS)) .build() .execute(); @@ -250,19 +263,17 @@ public void execute_withEatsProductFlowPriority_shouldLaunchEats() { public void execute_withMissingProductFlowPriority_shouldLaunchRides() { enableSupport(); String ridesPackageName = "com.ubercab"; - PackageInfo eatsPackageInfo = new PackageInfo(); - eatsPackageInfo.packageName = ridesPackageName; + PackageInfo ridesPackageInfo = new PackageInfo(); + ridesPackageInfo.packageName = ridesPackageName; when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)) - .thenReturn(Collections.singletonList(eatsPackageInfo)); - - Collection supportedAppTypes = ImmutableList.of(); + .thenReturn(Collections.singletonList(ridesPackageInfo)); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) - .productFlowPriority(supportedAppTypes) + .productFlowPriority(ImmutableList.of()) .build() .execute(); From c4adee7f5aa74fc6b98025d5f43a621b9c11b887 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 26 Oct 2018 14:25:50 -0700 Subject: [PATCH 089/165] Reorder maven repos for jcenter issue --- core-android/build.gradle | 2 +- gradle/verification.gradle | 2 ++ rides-android/build.gradle | 2 +- samples/login-sample/build.gradle | 2 +- samples/request-button-sample/build.gradle | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core-android/build.gradle b/core-android/build.gradle index 698fe64b..8f4979c9 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -16,8 +16,8 @@ buildscript { repositories { - jcenter() google() + jcenter() maven { url deps.build.repositories.plugins } } diff --git a/gradle/verification.gradle b/gradle/verification.gradle index a3fff45a..39c9b5dc 100644 --- a/gradle/verification.gradle +++ b/gradle/verification.gradle @@ -1,11 +1,13 @@ subprojects { buildscript { repositories { + google() jcenter() } } repositories { + google() jcenter() maven { url 'https://maven.google.com' diff --git a/rides-android/build.gradle b/rides-android/build.gradle index a89395b5..5ede030b 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -16,8 +16,8 @@ buildscript { repositories { - jcenter() google() + jcenter() maven { url deps.build.repositories.plugins } } diff --git a/samples/login-sample/build.gradle b/samples/login-sample/build.gradle index 194f45b1..92ab2e3e 100644 --- a/samples/login-sample/build.gradle +++ b/samples/login-sample/build.gradle @@ -17,8 +17,8 @@ buildscript { apply from: rootProject.file('gradle/dependencies.gradle') repositories { - jcenter() google() + jcenter() } dependencies { diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle index b6d6cbe8..d75ac96a 100644 --- a/samples/request-button-sample/build.gradle +++ b/samples/request-button-sample/build.gradle @@ -17,8 +17,8 @@ buildscript { apply from: rootProject.file('gradle/dependencies.gradle') repositories { - jcenter() google() + jcenter() } dependencies { From 14eb232e8df1731b0fdf2bc18e40903e9ce1d3ed Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Wed, 24 Oct 2018 15:26:06 -0400 Subject: [PATCH 090/165] Bump minimum supported eats version --- .../main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 9c232881..34b92131 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -58,7 +58,7 @@ public class SsoDeeplink implements Deeplink { @VisibleForTesting static final int MIN_UBER_RIDES_VERSION_SUPPORTED = 31302; @VisibleForTesting - static final int MIN_UBER_EATS_VERSION_SUPPORTED = 983; + static final int MIN_UBER_EATS_VERSION_SUPPORTED = 1085; private static final String URI_QUERY_CLIENT_ID = "client_id"; private static final String URI_QUERY_SCOPE = "scope"; From b084d4ada8bbc02fae56912898c2b7a0a3fab944 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Fri, 23 Nov 2018 18:38:43 -0800 Subject: [PATCH 091/165] Remove sudo: false from travis config https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 169982cc..9fb628d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,8 +46,6 @@ branches: notifications: email: false -sudo: false - cache: directories: - - $HOME/.gradle \ No newline at end of file + - $HOME/.gradle From a22f6772f434283bced50ea9fbfaa49f2d2e5415 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Thu, 6 Sep 2018 11:43:28 -0400 Subject: [PATCH 092/165] Update SsoDeeplink to include new parameters for new SSO Flow --- .../sdk/android/core/auth/SsoDeeplink.java | 107 ++++- .../android/core/auth/SsoDeeplinkTest.java | 424 ++++++++++++++---- 2 files changed, 425 insertions(+), 106 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 34b92131..5fcb8e9f 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -25,6 +25,8 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; @@ -60,15 +62,21 @@ public class SsoDeeplink implements Deeplink { @VisibleForTesting static final int MIN_UBER_EATS_VERSION_SUPPORTED = 1085; + @VisibleForTesting + static final int MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED = 35757; + private static final String URI_QUERY_CLIENT_ID = "client_id"; private static final String URI_QUERY_SCOPE = "scope"; private static final String URI_QUERY_PLATFORM = "sdk"; private static final String URI_QUERY_SDK_VERSION = "sdk_version"; + private static final String URI_QUERY_FLOW_TYPE = "flow_type"; + private static final String URI_QUERY_REDIRECT = "redirect_uri"; private static final String URI_HOST = "connect"; private final Activity activity; private final AppProtocol appProtocol; private final String clientId; + private final String redirectUri; private final Collection requestedScopes; private final Collection requestedCustomScopes; private final Collection productFlowPriority; @@ -78,6 +86,7 @@ private SsoDeeplink( @NonNull Activity activity, @NonNull AppProtocol appProtocol, @NonNull String clientId, + @NonNull String redirectUri, @NonNull Collection requestedScopes, @NonNull Collection requestedCustomScopes, @NonNull Collection productFlowPriority, @@ -85,6 +94,7 @@ private SsoDeeplink( this.activity = activity; this.appProtocol = appProtocol; this.clientId = clientId; + this.redirectUri = redirectUri; this.requestCode = requestCode; this.requestedScopes = requestedScopes; this.requestedCustomScopes = requestedCustomScopes; @@ -92,64 +102,104 @@ private SsoDeeplink( } /** - * Start {@link Activity#startActivityForResult(Intent, int)} with the right configurations. Use {@link Builder} - * to instantiate the object. + * {@code flowVersion} defaults to {@link FlowVersion#DEFAULT} * - * @throws IllegalStateException if compatible Uber app is not installed. Use {@link #isSupported()} to check. + * @see #execute(FlowVersion) */ @Override public void execute() { - checkState(isSupported(), "Single sign on is not supported on the device. " + + execute(FlowVersion.DEFAULT); + } + + /** + * Starts the deeplink with the configuration options specified by this object. Use {@link Builder} to construct + * an instance. + * + * @param flowVersion specifies which client implementation to use for handling the response to the SSO request + * @throws IllegalStateException if compatible Uber app is not installed or the deeplink is incorrectly configured. + * Use {@link #isSupported()} to check. + */ + void execute(@NonNull FlowVersion flowVersion) { + checkState(isSupported(flowVersion), "Single sign on is not supported on the device. " + "Please install or update to the latest version of Uber app."); Intent intent = new Intent(Intent.ACTION_VIEW); - final Uri deepLinkUri = createSsoUri(); + final Uri deepLinkUri = createSsoUri(flowVersion); intent.setData(deepLinkUri); List validatedPackages = new ArrayList<>(); if (productFlowPriority.isEmpty()) { validatedPackages.addAll( - appProtocol.getInstalledPackages(activity, UBER, getSupportedAppVersion(UBER))); + appProtocol.getInstalledPackages(activity, UBER, getSupportedAppVersion(UBER, flowVersion))); } else { for (SupportedAppType supportedAppType : productFlowPriority) { validatedPackages.addAll(appProtocol.getInstalledPackages( - activity, supportedAppType, getSupportedAppVersion(supportedAppType))); + activity, supportedAppType, getSupportedAppVersion(supportedAppType, flowVersion))); } } if (!validatedPackages.isEmpty()) { intent.setPackage(validatedPackages.get(0).packageName); } - activity.startActivityForResult(intent, requestCode); + if (flowVersion == FlowVersion.DEFAULT) { + activity.startActivityForResult(intent, requestCode); + } else { + activity.startActivity(intent); + } } - private Uri createSsoUri() { + private Uri createSsoUri(@NonNull FlowVersion flowVersion) { String scopes = AuthUtils.scopeCollectionToString(requestedScopes); if (!requestedCustomScopes.isEmpty()) { scopes = AuthUtils.mergeScopeStrings(scopes, AuthUtils.customScopeCollectionToString(requestedCustomScopes)); } - return new Uri.Builder().scheme(Deeplink.DEEPLINK_SCHEME) + Uri.Builder uriBuilder = new Uri.Builder().scheme(Deeplink.DEEPLINK_SCHEME) .authority(URI_HOST) .appendQueryParameter(URI_QUERY_CLIENT_ID, clientId) .appendQueryParameter(URI_QUERY_SCOPE, scopes) .appendQueryParameter(URI_QUERY_PLATFORM, AppProtocol.PLATFORM) - .appendQueryParameter(URI_QUERY_SDK_VERSION, BuildConfig.VERSION_NAME) - .build(); + .appendQueryParameter(URI_QUERY_FLOW_TYPE, flowVersion.name()) + .appendQueryParameter(URI_QUERY_REDIRECT, redirectUri) + .appendQueryParameter(URI_QUERY_SDK_VERSION, BuildConfig.VERSION_NAME); + return uriBuilder.build(); } /** - * Check if SSO deep linking is supported in this device. + * {@code flowVersion} defaults to {@link FlowVersion#DEFAULT} * - * @return true if package name and minimum version conditions are met. + * @see #isSupported(FlowVersion) */ @Override public boolean isSupported() { + return isSupported(FlowVersion.DEFAULT); + } + + /** + * Check if SSO deep linking is supported in this device. + * + * @param flowVersion specifies which client implementation to validate + * @return true if package name, minimum version, and redirect uri requirements for flowVersion are met + */ + boolean isSupported(@NonNull FlowVersion flowVersion) { + if (flowVersion == FlowVersion.REDIRECT_TO_SDK) { + Intent redirectIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(redirectUri)); + redirectIntent.setPackage(activity.getPackageName()); + List resolveInfoList = activity.getPackageManager().queryIntentActivities( + redirectIntent, PackageManager.MATCH_DEFAULT_ONLY); + if (resolveInfoList.isEmpty()) { + return false; + } + } + if (productFlowPriority.isEmpty()) { - return appProtocol.isInstalled(activity, UBER, getSupportedAppVersion(UBER)); + return appProtocol.isInstalled(activity, UBER, getSupportedAppVersion(UBER, flowVersion)); } else { for (SupportedAppType supportedAppType : productFlowPriority) { - if (appProtocol.isInstalled(activity, supportedAppType, getSupportedAppVersion(supportedAppType))) { + if (appProtocol.isInstalled( + activity, + supportedAppType, + getSupportedAppVersion(supportedAppType, flowVersion))) { return true; } } @@ -157,9 +207,11 @@ public boolean isSupported() { } } - private static int getSupportedAppVersion(SupportedAppType supportedAppType) { + private static int getSupportedAppVersion(SupportedAppType supportedAppType, FlowVersion flowVersion) { if (UBER == supportedAppType) { - return MIN_UBER_RIDES_VERSION_SUPPORTED; + return flowVersion == FlowVersion.REDIRECT_TO_SDK + ? MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED + : MIN_UBER_RIDES_VERSION_SUPPORTED; } else if (UBER_EATS == supportedAppType) { return MIN_UBER_EATS_VERSION_SUPPORTED; } @@ -171,6 +223,7 @@ public static class Builder { private final Activity activity; private AppProtocol appProtocol; private String clientId; + private String redirectUri; private Collection requestedScopes; private Collection requestedCustomScopes; private Collection productFlowPriority; @@ -216,6 +269,11 @@ Builder appProtocol(@NonNull AppProtocol appProtocol) { return this; } + Builder redirectUri(@NonNull String redirectUri) { + this.redirectUri = redirectUri; + return this; + } + public SsoDeeplink build() { checkNotNull(clientId, "Client Id must be set"); @@ -237,13 +295,26 @@ public SsoDeeplink build() { productFlowPriority = new ArrayList<>(); } + if (redirectUri == null) { + redirectUri = activity.getPackageName().concat(".uberauth://redirect"); + } + return new SsoDeeplink(activity, appProtocol, clientId, + redirectUri, requestedScopes, requestedCustomScopes, productFlowPriority, requestCode); } } + + /** + * Defines which client implementation of the SSO flow to use. This determines how the response from the SSO + * request is returned to the calling application. + */ + enum FlowVersion { + DEFAULT, REDIRECT_TO_SDK + } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 14fa25c3..862b5ec7 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -23,21 +23,27 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; +import android.content.pm.ResolveInfo; import android.net.Uri; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; -import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.AppProtocol; + import com.uber.sdk.core.auth.Scope; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.res.builder.RobolectricPackageManager; +import org.robolectric.shadows.ShadowResolveInfo; import java.util.Arrays; import java.util.Collection; @@ -46,176 +52,391 @@ import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; +import static com.uber.sdk.android.core.auth.SsoDeeplink.FlowVersion.DEFAULT; +import static com.uber.sdk.android.core.auth.SsoDeeplink.FlowVersion.REDIRECT_TO_SDK; import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED; import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED; +import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; public class SsoDeeplinkTest extends RobolectricTestBase { private static final String CLIENT_ID = "MYCLIENTID"; private static final Set GENERAL_SCOPES = Sets.newHashSet(Scope.HISTORY, Scope.PROFILE); private static final int REQUEST_CODE = 1234; + private static final String REDIRECT_URI = "com.example.app://redirect"; - private static final String DEFAULT_REGION = - "uber://connect?client_id=MYCLIENTID&scope=profile%20history&sdk=android&sdk_version=" + private static final String DEFAULT_URI = + "uber://connect?client_id=MYCLIENTID&scope=profile%20history&sdk=android&flow_type=DEFAULT" + + "&redirect_uri=com.example.app%3A%2F%2Fredirect&sdk_version=" + BuildConfig.VERSION_NAME; - @Mock AppProtocol appProtocol; Activity activity; + RobolectricPackageManager packageManager; + + ResolveInfo resolveInfo; + + Intent redirectIntent; + + SsoDeeplink ssoDeeplink; + @Before public void setUp() { activity = spy(Robolectric.setupActivity(Activity.class)); + + redirectIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(REDIRECT_URI)); + redirectIntent.setPackage(activity.getPackageName()); + resolveInfo = ShadowResolveInfo.newResolveInfo("", activity.getPackageName()); + packageManager = RuntimeEnvironment.getRobolectricPackageManager(); + packageManager.addResolveInfoForIntent(redirectIntent, resolveInfo); + + ssoDeeplink = new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .build(); } @Test - public void isSupported_appInstalled_shouldBeTrue() { - when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); + public void isSupported_ridesNotInstalled_withoutProductPriority_shouldBeFalse() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + + assertThat(ssoDeeplink.isSupported()).isFalse(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER_EATS), anyInt()); + } + + @Test + public void isSupported_ridesNotInstalled_withoutProductPriority_andRedirectToSdkFlowVersion_shouldBeFalse() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED)).thenReturn(false); + + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isFalse(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER_EATS), anyInt()); + } + + @Test + public void isSupported_ridesInstalled_withoutProductPriority_shouldBeTrue() { + enableSupport(DEFAULT); + + assertThat(ssoDeeplink.isSupported()).isTrue(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER_EATS), anyInt()); + } + + @Test + public void isSupported_ridesInstalled_withoutProductPriority_andRedirectToSdkFlowVersion_shouldBeTrue() { + enableSupport(REDIRECT_TO_SDK); + + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isTrue(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER_EATS), anyInt()); + } + + @Test + public void isSupported_eatsNotInstalled_withEatsProductPriority_shouldBeFalse() { when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .productFlowPriority(ImmutableList.of(UBER_EATS)) .build(); - assertThat(link.isSupported()).isTrue(); + assertThat(ssoDeeplink.isSupported()).isFalse(); + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isFalse(); + + verify(appProtocol, times(2)).isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER), anyInt()); } @Test - public void isSupported_eatsAppInstalled_withoutProductPriority_shouldBeFalse() { - when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + public void isSupported_eatsInstalled_withEatsProductPriority_shouldBeTrue() { when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .productFlowPriority(ImmutableList.of(UBER_EATS)) .build(); - assertThat(link.isSupported()).isFalse(); + assertThat(ssoDeeplink.isSupported()).isTrue(); + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isTrue(); + + verify(appProtocol, times(2)).isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER), anyInt()); } @Test - public void isSupported_eatsAppInstalled_withProductPriority_shouldBeTrue() { + public void isSupported_noneInstalled_withCombinedProductPriority_shouldBeFalse() { when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); - when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); - final SsoDeeplink link = new SsoDeeplink.Builder(activity) + ssoDeeplink = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) - .productFlowPriority(ImmutableList.of(UBER_EATS)) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) .build(); - assertThat(link.isSupported()).isTrue(); + assertThat(ssoDeeplink.isSupported()).isFalse(); + + InOrder orderVerifier = inOrder(appProtocol); + orderVerifier.verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + orderVerifier.verify(appProtocol).isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); } @Test - public void isSupported_appNotInstalled_shouldBeFalse() { - when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + public void isSupported_noneInstalled_withCombinedProductPriority_andRedirectToSdkFlowVersion_shouldBeFalse() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED)).thenReturn(false); when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); + ssoDeeplink = new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) + .build(); + + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isFalse(); + + InOrder orderVerifier = inOrder(appProtocol); + orderVerifier.verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED); + orderVerifier.verify(appProtocol).isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); + } + + @Test + public void isSupported_bothAppsInstalled_withCombinedProductPriority_shouldBeTrue() { + enableSupport(DEFAULT); + + ssoDeeplink = new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) + .build(); + + assertThat(ssoDeeplink.isSupported()).isTrue(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER_EATS), anyInt()); + } + + @Test + public void isSupported_bothAppsInstalled_withCombinedProductPriority_andRedirectToSdkFlowVersion_shouldBeTrue() { + enableSupport(REDIRECT_TO_SDK); + + ssoDeeplink = new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .appProtocol(appProtocol) + .activityRequestCode(REQUEST_CODE) + .redirectUri(REDIRECT_URI) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) + .build(); + + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isTrue(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED); + verify(appProtocol, never()).isInstalled(any(Context.class), eq(UBER_EATS), anyInt()); + } + + @Test + public void isSupported_eatsAppInstalled_withProductPriority_shouldBeTrue() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); + final SsoDeeplink link = new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) + .productFlowPriority(ImmutableList.of(UBER_EATS)) .build(); - assertThat(link.isSupported()).isFalse(); + assertThat(link.isSupported()).isTrue(); } @Test - public void execute_withInstalledPackage_shouldSetPackage() { + public void isSupported_withRidesAppInstalled_andDefaultFlowVersion_andAboveMinDefaultFlowVersion_shouldBeTrue() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); + + assertThat(ssoDeeplink.isSupported()).isTrue(); + + verify(appProtocol).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + } + + @Test + public void isSupported_withRedirectToSdkFlowVersion_andCantResolveRedirectIntent_shouldBeFalse() { + enableSupport(REDIRECT_TO_SDK); + packageManager.removeResolveInfosForIntent(redirectIntent, activity.getPackageName()); + + assertThat(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).isFalse(); + + verify(appProtocol, never()).isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED); + verify(appProtocol, never()).isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED); + } + + @Test + public void execute_withRidesInstalled_andDefaultFlow_andNoProductPriority_shouldSetPackageAndStartActivityForResult() { + enableSupport(DEFAULT); + String packageName = "PACKAGE_NAME"; PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; - when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)) .thenReturn(Collections.singletonList(packageInfo)); - new SsoDeeplink.Builder(activity) - .clientId(CLIENT_ID) - .scopes(Scope.HISTORY, Scope.PROFILE) - .activityRequestCode(REQUEST_CODE) - .appProtocol(appProtocol) - .build() - .execute(); + ssoDeeplink.execute(); + + verify(appProtocol).getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED); + verify(appProtocol, never()).getInstalledPackages(any(Context.class), eq(UBER_EATS), anyInt()); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivityForResult(intentCaptor.capture(), eq(REQUEST_CODE)); Intent intent = intentCaptor.getValue(); assertThat(intent.getPackage()).isEqualTo(packageName); + assertThat(intent.getData().toString()).isEqualTo(DEFAULT_URI); } @Test - public void execute_withoutRegion_shouldUseWorld() { - enableSupport(); + public void execute_withRidesInstalled_andRedirectToSdkFlow_andNoProductPriority_shouldSetPackageAndStartActivity() { + enableSupport(REDIRECT_TO_SDK); + + String packageName = "PACKAGE_NAME"; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + + when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED)) + .thenReturn(Collections.singletonList(packageInfo)); + + ssoDeeplink.execute(REDIRECT_TO_SDK); + verify(appProtocol).getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED); + verify(appProtocol, never()).getInstalledPackages(any(Context.class), eq(UBER_EATS), anyInt()); + + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivity(intentCaptor.capture()); + Intent intent = intentCaptor.getValue(); + + String expectedUri = + "uber://connect?client_id=MYCLIENTID&scope=profile%20history&sdk=android&flow_type=REDIRECT_TO_SDK" + + "&redirect_uri=com.example.app%3A%2F%2Fredirect&sdk_version=" + + BuildConfig.VERSION_NAME; + + assertThat(intent.getData().toString()).isEqualTo(expectedUri); + assertThat(intent.getPackage()).isEqualTo(packageName); + } + + @Test + public void execute_withEatsProductFlowPriority_shouldLaunchEats() { + enableSupport(DEFAULT); + + String eatsPackageName = "com.ubercab.eats"; + PackageInfo eatsPackageInfo = new PackageInfo(); + eatsPackageInfo.packageName = eatsPackageName; + + when(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)) + .thenReturn(Collections.singletonList(eatsPackageInfo)); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) - .scopes(Scope.HISTORY, Scope.PROFILE) .activityRequestCode(REQUEST_CODE) + .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) + .productFlowPriority(ImmutableList.of(UBER_EATS)) .build() .execute(); - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); - verify(activity).startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture()); - - final Uri uri = intentCaptor.getValue().getData(); + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); - assertThat(uri.toString()).isEqualTo(DEFAULT_REGION); - assertThat(requestCodeCaptor.getValue()).isEqualTo(REQUEST_CODE); + assertThat(intentArgumentCaptor.getValue().getPackage()).isEqualTo(eatsPackageName); } @Test - public void execute_withoutRequestCode_shouldUseDefaultRequstCode() { - enableSupport(); + public void execute_withCombinedProductFlowPriority_andBothAppsInstalled_shouldLaunchFirstPriorityApp() { + enableSupport(DEFAULT); + + String eatsPackageName = "com.ubercab.eats"; + PackageInfo eatsPackageInfo = new PackageInfo(); + eatsPackageInfo.packageName = eatsPackageName; + + String ridesPackageName = "com.ubercab"; + PackageInfo ridesPackageInfo = new PackageInfo(); + ridesPackageInfo.packageName = ridesPackageName; + + when(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)) + .thenReturn(Collections.singletonList(eatsPackageInfo)); + when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)) + .thenReturn(Collections.singletonList(ridesPackageInfo)); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) + .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) .build() .execute(); - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - final ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); - verify(activity).startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture()); - - Uri uri = intentCaptor.getValue().getData(); + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); - assertThat(uri.toString()).isEqualTo(DEFAULT_REGION); - assertThat(requestCodeCaptor.getValue()).isEqualTo(LoginManager.REQUEST_CODE_LOGIN_DEFAULT); + assertThat(intentArgumentCaptor.getValue().getPackage()).isEqualTo(ridesPackageName); } - - @Test(expected = IllegalStateException.class) - public void execute_withoutScopes_shouldFail() { - enableSupport(); + @Test + public void execute_withoutRequestCode_shouldUseDefaultRequestCode() { + enableSupport(DEFAULT); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) - .activityRequestCode(REQUEST_CODE) + .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) + .redirectUri(REDIRECT_URI) .build() .execute(); + + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor requestCodeCaptor = ArgumentCaptor.forClass(Integer.class); + verify(activity).startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture()); + + Uri uri = intentCaptor.getValue().getData(); + + assertThat(uri.toString()).isEqualTo(DEFAULT_URI); + assertThat(requestCodeCaptor.getValue()).isEqualTo(LoginManager.REQUEST_CODE_LOGIN_DEFAULT); } @Test public void execute_withScopesAndCustomScopes_shouldSucceed() { - enableSupport(); + enableSupport(DEFAULT); Collection collection = Arrays.asList("sample", "test"); @@ -236,56 +457,43 @@ public void execute_withScopesAndCustomScopes_shouldSucceed() { } @Test - public void execute_withEatsProductFlowPriority_shouldLaunchEats() { - enableSupport(); - String eatsPackageName = "com.ubercab.eats"; - PackageInfo eatsPackageInfo = new PackageInfo(); - eatsPackageInfo.packageName = eatsPackageName; - when(appProtocol.getInstalledPackages(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)) - .thenReturn(Collections.singletonList(eatsPackageInfo)); + public void execute_withoutRedirectUri_shouldUseDefaultUri() { + enableSupport(REDIRECT_TO_SDK); + packageManager.removeResolveInfosForIntent(redirectIntent, activity.getPackageName()); + String expectedRedirectUri = activity.getPackageName().concat(".uberauth://redirect"); + Intent expectedIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(expectedRedirectUri)); + expectedIntent.setPackage(activity.getPackageName()); + packageManager.addResolveInfoForIntent(expectedIntent, resolveInfo); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) - .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) - .productFlowPriority(ImmutableList.of(UBER_EATS)) .build() - .execute(); + .execute(REDIRECT_TO_SDK); ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); + verify(activity).startActivity(intentArgumentCaptor.capture()); - assertThat(intentArgumentCaptor.getValue().getPackage()).isEqualTo(eatsPackageName); + Uri uri = intentArgumentCaptor.getValue().getData(); + assertThat(uri.getQueryParameter("redirect_uri")).isEqualTo(expectedRedirectUri); } - @Test - public void execute_withMissingProductFlowPriority_shouldLaunchRides() { - enableSupport(); - String ridesPackageName = "com.ubercab"; - PackageInfo ridesPackageInfo = new PackageInfo(); - ridesPackageInfo.packageName = ridesPackageName; - when(appProtocol.getInstalledPackages(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)) - .thenReturn(Collections.singletonList(ridesPackageInfo)); + @Test(expected = IllegalStateException.class) + public void execute_withoutScopes_shouldFail() { + enableSupport(DEFAULT); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .activityRequestCode(REQUEST_CODE) - .scopes(GENERAL_SCOPES) .appProtocol(appProtocol) - .productFlowPriority(ImmutableList.of()) .build() .execute(); - - ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); - - assertThat(intentArgumentCaptor.getValue().getPackage()).isEqualTo(ridesPackageName); } @Test(expected = NullPointerException.class) public void execute_withoutClientId_shouldFail() { - enableSupport(); + enableSupport(DEFAULT); new SsoDeeplink.Builder(activity) .scopes(GENERAL_SCOPES) @@ -296,19 +504,59 @@ public void execute_withoutClientId_shouldFail() { } @Test(expected = IllegalStateException.class) - public void execute_withoutAppInstalled_shouldFail() { + public void execute_withRidesBelowMinVersion_noProductPriority_shouldFail() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); + + new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .build() + .execute(); + } + + @Test(expected = IllegalStateException.class) + public void execute_withBothAppsBelowMinVersion_andCombinedProductPriority_shouldFail() { when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(false); when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .scopes(GENERAL_SCOPES) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) .build() .execute(); } - private void enableSupport() { - when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_SUPPORTED)).thenReturn(true); + @Test(expected = IllegalStateException.class) + public void execute_withBothAppsBelowMinRedirectToSdkVersion_andCombinedProductPriority_shouldFail() { + when(appProtocol.isInstalled(activity, UBER, MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED)).thenReturn(false); + when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(false); + + new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .productFlowPriority(ImmutableList.of(UBER, UBER_EATS)) + .build() + .execute(REDIRECT_TO_SDK); + } + + @Test(expected = IllegalStateException.class) + public void execute_withRedirectToSdkFlowVersion_andCantResolveRedirectIntent_shouldFail() { + enableSupport(REDIRECT_TO_SDK); + packageManager.removeResolveInfosForIntent(redirectIntent, activity.getPackageName()); + + new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .scopes(GENERAL_SCOPES) + .build() + .execute(REDIRECT_TO_SDK); + } + + private void enableSupport(SsoDeeplink.FlowVersion flowVersion) { + int ridesMinVersion = flowVersion == REDIRECT_TO_SDK + ? MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED + : MIN_UBER_RIDES_VERSION_SUPPORTED; + when(appProtocol.isInstalled(activity, UBER, ridesMinVersion)).thenReturn(true); when(appProtocol.isInstalled(activity, UBER_EATS, MIN_UBER_EATS_VERSION_SUPPORTED)).thenReturn(true); } } From 1b9131d800f0d00dec064f305972c65c66b10f98 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Thu, 6 Sep 2018 11:47:30 -0400 Subject: [PATCH 093/165] Update LoginActivity to be able to accept parameters for new SSO Flow --- .../sdk/android/core/auth/LoginActivity.java | 111 +++++-- .../android/core/auth/SsoDeeplinkFactory.java | 16 + .../android/core/auth/LoginActivityTest.java | 274 +++++++++++++++--- 3 files changed, 340 insertions(+), 61 deletions(-) create mode 100644 core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 7f33ef6e..b4f51cd6 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -39,7 +39,9 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.R; +import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; @@ -47,19 +49,31 @@ * {@link android.app.Activity} that shows web view for Uber user authentication and authorization. */ public class LoginActivity extends Activity { + @VisibleForTesting + static final String USER_AGENT = String.format("core-android-v%s-login_manager", BuildConfig.VERSION_NAME); static final String EXTRA_RESPONSE_TYPE = "RESPONSE_TYPE"; static final String EXTRA_SESSION_CONFIGURATION = "SESSION_CONFIGURATION"; static final String EXTRA_FORCE_WEBVIEW = "FORCE_WEBVIEW"; + static final String EXTRA_SSO_ENABLED = "SSO_ENABLED"; + static final String EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED = "REDIRECT_TO_PLAY_STORE_ENABLED"; static final String ERROR = "error"; - private WebView webView; private ResponseType responseType; private SessionConfiguration sessionConfiguration; - private LoginManager loginManager; private boolean authStarted; + @VisibleForTesting + WebView webView; + + @VisibleForTesting + SsoDeeplinkFactory ssoDeeplinkFactory = new SsoDeeplinkFactory(); + + @VisibleForTesting + CustomTabsHelper customTabsHelper = new CustomTabsHelper(); + + /** * Create an {@link Intent} to pass to this activity * @@ -93,10 +107,34 @@ public static Intent newIntent( @NonNull ResponseType responseType, boolean forceWebview) { + return newIntent(context, sessionConfiguration, responseType, forceWebview, false, false); + } + + /** + * Create an {@link Intent} to pass to this activity + * + * @param context the {@link Context} for the intent + * @param sessionConfiguration to be used for gather clientId + * @param responseType that is expected + * @param forceWebview Forced to use old webview instead of chrometabs + * @param isSsoEnabled specifies whether to attempt login with SSO + * @return an intent that can be passed to this activity + */ + @NonNull + static Intent newIntent( + @NonNull Context context, + @NonNull SessionConfiguration sessionConfiguration, + @NonNull ResponseType responseType, + boolean forceWebview, + boolean isSsoEnabled, + boolean isRedirectToPlayStoreEnabled) { + final Intent data = new Intent(context, LoginActivity.class) .putExtra(EXTRA_SESSION_CONFIGURATION, sessionConfiguration) .putExtra(EXTRA_RESPONSE_TYPE, responseType) - .putExtra(EXTRA_FORCE_WEBVIEW, forceWebview); + .putExtra(EXTRA_FORCE_WEBVIEW, forceWebview) + .putExtra(EXTRA_SSO_ENABLED, isSsoEnabled) + .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled); return data; } @@ -152,28 +190,44 @@ protected void init() { } protected void loadUrl() { - sessionConfiguration = (SessionConfiguration) getIntent().getSerializableExtra(EXTRA_SESSION_CONFIGURATION); - if (sessionConfiguration == null) { - onError(AuthenticationError.UNAVAILABLE); + Intent intent = getIntent(); + + sessionConfiguration = (SessionConfiguration) intent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION); + responseType = (ResponseType) intent.getSerializableExtra(EXTRA_RESPONSE_TYPE); + + if (!validateRequestParams()) { return; } - if ((sessionConfiguration.getScopes() == null || sessionConfiguration.getScopes().isEmpty()) - && (sessionConfiguration.getCustomScopes() == null || sessionConfiguration.getCustomScopes().isEmpty())) { - onError(AuthenticationError.INVALID_SCOPE); + String redirectUri = sessionConfiguration.getRedirectUri() != null ? sessionConfiguration + .getRedirectUri() : getApplicationContext().getPackageName() + "uberauth"; + + if (intent.getBooleanExtra(EXTRA_SSO_ENABLED, false)) { + SsoDeeplink ssoDeeplink = ssoDeeplinkFactory.getSsoDeeplink(this, sessionConfiguration); + + if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)) { + ssoDeeplink.execute(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK); + } else { + onError(AuthenticationError.INVALID_REDIRECT_URI); + } return; } - responseType = (ResponseType) getIntent().getSerializableExtra(EXTRA_RESPONSE_TYPE); - if (responseType == null) { - onError(AuthenticationError.UNAVAILABLE); + boolean forceWebview = intent.getBooleanExtra(EXTRA_FORCE_WEBVIEW, false); + boolean isRedirectToPlayStoreEnabled = intent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false); + if (responseType == ResponseType.CODE) { + loadWebPage(redirectUri, ResponseType.CODE, sessionConfiguration, forceWebview); + } else if (responseType == ResponseType.TOKEN && !(AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes()) + && isRedirectToPlayStoreEnabled)) { + loadWebPage(redirectUri, ResponseType.TOKEN, sessionConfiguration, forceWebview); + } else { + redirectToInstallApp(this); } + } - String redirectUri = sessionConfiguration.getRedirectUri() != null ? sessionConfiguration - .getRedirectUri() : getApplicationContext().getPackageName() + "uberauth"; - + protected void loadWebPage(String redirectUri, ResponseType responseType, SessionConfiguration sessionConfiguration, boolean forceWebview) { String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration); - if (getIntent().getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)) { + if (forceWebview) { loadWebview(url, redirectUri); } else { loadChrometab(url); @@ -213,7 +267,6 @@ protected void loadWebview(String url, String redirectUri) { protected void loadChrometab(String url) { final CustomTabsIntent intent = new CustomTabsIntent.Builder().build(); - CustomTabsHelper customTabsHelper = new CustomTabsHelper(); customTabsHelper.openCustomTab(this, intent, Uri.parse(url), new CustomTabsHelper .BrowserFallback()); } @@ -257,6 +310,30 @@ void onCodeReceived(Uri uri) { } } + private boolean validateRequestParams() { + if (sessionConfiguration == null) { + onError(AuthenticationError.INVALID_PARAMETERS); + return false; + } + + if ((sessionConfiguration.getScopes() == null || sessionConfiguration.getScopes().isEmpty()) + && (sessionConfiguration.getCustomScopes() == null || sessionConfiguration.getCustomScopes().isEmpty())) { + onError(AuthenticationError.INVALID_SCOPE); + return false; + } + + if (responseType == null) { + onError(AuthenticationError.INVALID_RESPONSE_TYPE); + return false; + } + + return true; + } + + private void redirectToInstallApp(@NonNull Activity activity) { + new SignupDeeplink(activity, sessionConfiguration.getClientId(), USER_AGENT).execute(); + } + /** * Custom {@link WebViewClient} for authorization. */ diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java new file mode 100644 index 00000000..ca3572d5 --- /dev/null +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java @@ -0,0 +1,16 @@ +package com.uber.sdk.android.core.auth; + +import android.app.Activity; +import com.uber.sdk.core.client.SessionConfiguration; + +public class SsoDeeplinkFactory { + + SsoDeeplink getSsoDeeplink(Activity activity, SessionConfiguration sessionConfiguration) { + return new SsoDeeplink.Builder(activity) + .clientId(sessionConfiguration.getClientId()) + .scopes(sessionConfiguration.getScopes()) + .customScopes(sessionConfiguration.getCustomScopes()) + .redirectUri(sessionConfiguration.getRedirectUri()) + .build(); + } +} diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index c98feecb..eb117579 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -26,21 +26,30 @@ import android.content.Intent; import android.net.Uri; +import android.support.customtabs.CustomTabsIntent; +import com.google.common.collect.Sets; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import org.robolectric.Robolectric; +import org.robolectric.Shadows; import org.robolectric.shadows.ShadowActivity; +import org.robolectric.shadows.ShadowWebView; import org.robolectric.util.ActivityController; +import java.util.Set; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.*; import static org.robolectric.Shadows.shadowOf; /** @@ -51,18 +60,232 @@ public class LoginActivityTest extends RobolectricTestBase { private static final String REDIRECT_URI = "localHost1234"; private static final String CLIENT_ID = "clientId1234"; private static final String CODE = "auth123Code"; + private static final Set GENERAL_SCOPES = Sets.newHashSet(Scope.HISTORY, Scope.PROFILE); + private static final Set MIXED_SCOPES = Sets.newHashSet(Scope.REQUEST, Scope.PROFILE, Scope.PAYMENT_METHODS); + private static final String SIGNUP_DEEPLINK_URL = "https://m.uber.com/sign-up?client_id=" + CLIENT_ID + "&user-agent=" + LoginActivity.USER_AGENT; + private LoginActivity loginActivity; + private SessionConfiguration loginConfiguration; + + @Mock + SsoDeeplink ssoDeeplink; + + @Mock + SsoDeeplinkFactory ssoDeeplinkFactory; + + @Mock + CustomTabsHelper customTabsHelper; @Before public void setup() { - SessionConfiguration loginConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID).setRedirectUri(REDIRECT_URI).build(); + loginConfiguration = new SessionConfiguration.Builder() + .setClientId(CLIENT_ID) + .setRedirectUri(REDIRECT_URI) + .setScopes(GENERAL_SCOPES) + .build(); Intent data = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN); - loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(data).create().get(); + loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(data).get(); + + when(ssoDeeplinkFactory.getSsoDeeplink(any(LoginActivity.class), any(SessionConfiguration.class))).thenReturn(ssoDeeplink); + } + + @Test + public void onLoginLoad_withEmptySessionConfiguration_shouldReturnErrorResultIntent() { + ActivityController controller = Robolectric.buildActivity(LoginActivity.class); + controller.create(); + + ShadowActivity shadowActivity = shadowOf(controller.get()); + + assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); + assertThat(shadowActivity.getResultIntent()).isNotNull(); + assertThat(getErrorFromIntent(shadowActivity.getResultIntent())) + .isEqualTo(AuthenticationError.INVALID_PARAMETERS); + assertThat(shadowActivity.isFinishing()).isTrue(); + } + + @Test + public void onLoginLoad_withEmptyScopes_shouldReturnErrorResultIntent() { + Intent intent = new Intent(); + intent.putExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION, new SessionConfiguration.Builder().setClientId(CLIENT_ID).build()); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class) + .withIntent(intent) + .create(); + + ShadowActivity shadowActivity = shadowOf(controller.get()); + + assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); + assertThat(shadowActivity.getResultIntent()).isNotNull(); + assertThat(getErrorFromIntent(shadowActivity.getResultIntent())) + .isEqualTo(AuthenticationError.INVALID_SCOPE); + assertThat(shadowActivity.isFinishing()).isTrue(); + } + + @Test + public void onLoginLoad_withNullResponseType_shouldReturnErrorResultIntent() { + Intent intent = new Intent(); + intent.putExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION, loginConfiguration); + intent.putExtra(LoginActivity.EXTRA_RESPONSE_TYPE, (ResponseType) null); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class) + .withIntent(intent) + .create(); + + ShadowActivity shadowActivity = shadowOf(controller.get()); + + assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); + assertThat(shadowActivity.getResultIntent()).isNotNull(); + assertThat(getErrorFromIntent(shadowActivity.getResultIntent())) + .isEqualTo(AuthenticationError.INVALID_RESPONSE_TYPE); + assertThat(shadowActivity.isFinishing()).isTrue(); + } + + @Test + public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, false, true, true); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + loginActivity = controller.get(); + loginActivity.ssoDeeplinkFactory = ssoDeeplinkFactory; + + when(ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)).thenReturn(true); + + controller.create(); + + verify(ssoDeeplink).execute(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK); + } + + @Test + public void onLoginLoad_withSsoEnabled_andNotSupported_shouldReturnErrorResultIntent() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, false, true, true); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + loginActivity = controller.get(); + loginActivity.ssoDeeplinkFactory = ssoDeeplinkFactory; + ShadowActivity shadowActivity = shadowOf(loginActivity); + when(ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)).thenReturn(false); + + controller.create(); + + assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); + assertThat(shadowActivity.getResultIntent()).isNotNull(); + assertThat(getErrorFromIntent(shadowActivity.getResultIntent())) + .isEqualTo(AuthenticationError.INVALID_REDIRECT_URI); + assertThat(shadowActivity.isFinishing()).isTrue(); } @Test - public void onLoginLoad_whenAccessTokenGenerated_shouldReturnAccessTokenResult() { + public void onLoginLoad_withResponseTypeCode_andForceWebview_shouldLoadWebview() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.CODE, true); + + loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get(); + ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); + + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration); + assertThat(webview.getLastLoadedUrl()).isEqualTo(expectedUrl); + } + + @Test + public void onLoginLoad_withResponseTypeCode_andNotForceWebview_shouldLoadChrometab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.CODE, false); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + loginActivity = controller.get(); + loginActivity.customTabsHelper = customTabsHelper; + controller.create(); + + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + public void onLoginLoad_withResponseTypeToken_andForceWebview_andGeneralScopes_shouldLoadWebview() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, true); + + loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get(); + ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); + + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); + assertThat(webview.getLastLoadedUrl()).isEqualTo(expectedUrl); + } + + @Test + public void onLoginLoad_withResponseTypeToken_andNotForceWebview_andGeneralScopes_shouldLoadChrometab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, false); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + loginActivity = controller.get(); + loginActivity.customTabsHelper = customTabsHelper; + controller.create(); + + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + public void onLoginLoad_withResponseTypeToken_andForceWebview_andPrivilegedScopes_andRedirectToPlayStoreDisabled_shouldLoadWebview() { + loginConfiguration = new SessionConfiguration.Builder() + .setClientId(CLIENT_ID) + .setRedirectUri(REDIRECT_URI) + .setScopes(MIXED_SCOPES) + .build(); + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, true); + + loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get(); + ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); + + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); + assertThat(webview.getLastLoadedUrl()).isEqualTo(expectedUrl); + } + + @Test + public void onLoginLoad_withResponseTypeToken_andNotForceWebview_andPrivilegedScopes_andRedirectToPlayStoreDisabled_shouldLoadChrometab() { + loginConfiguration = new SessionConfiguration.Builder() + .setClientId(CLIENT_ID) + .setRedirectUri(REDIRECT_URI) + .setScopes(MIXED_SCOPES) + .build(); + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, false); + + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + loginActivity = controller.get(); + loginActivity.customTabsHelper = customTabsHelper; + controller.create(); + + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToPlayStoreEnabled_shouldRedirectToPlayStore() { + loginConfiguration = new SessionConfiguration.Builder() + .setClientId(CLIENT_ID) + .setRedirectUri(REDIRECT_URI) + .setScopes(MIXED_SCOPES) + .build(); + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.TOKEN, true, false, true); + + ShadowActivity shadowActivity = shadowOf(Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get()); + + final Intent signupDeeplinkIntent = shadowActivity.peekNextStartedActivity(); + assertThat(signupDeeplinkIntent.getData().toString()).isEqualTo(SIGNUP_DEEPLINK_URL); + } + + @Test + public void onTokenReceived_shouldReturnAccessTokenResult() { String tokenString = "accessToken1234"; String redirectUrl = REDIRECT_URI + "?access_token=accessToken1234&expires_in=" + 2592000 @@ -85,20 +308,20 @@ public void onLoginLoad_whenAccessTokenGenerated_shouldReturnAccessTokenResult() } @Test - public void onLoginLoad_whenErrorOccurs_shouldReturnErrorResultIntent() { + public void onError_shouldReturnErrorResultIntent() { ShadowActivity shadowActivity = shadowOf(loginActivity); loginActivity.onError(AuthenticationError.MISMATCHING_REDIRECT_URI); assertThat(shadowActivity.getResultIntent()).isNotNull(); assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); - assertThat(getScopeFromIntent(shadowActivity.getResultIntent())) + assertThat(getErrorFromIntent(shadowActivity.getResultIntent())) .isEqualTo(AuthenticationError.MISMATCHING_REDIRECT_URI); assertThat(shadowActivity.isFinishing()).isTrue(); } @Test - public void onLoginLoad_whenCodeReturned_shouldReturnErrorResultIntent() { + public void onCodeReceived_shouldReturnResultIntentWithCode() { ShadowActivity shadowActivity = shadowOf(loginActivity); String redirectUrl = REDIRECT_URI + "?code=" + CODE; @@ -112,44 +335,7 @@ public void onLoginLoad_whenCodeReturned_shouldReturnErrorResultIntent() { assertThat(shadowActivity.isFinishing()).isTrue(); } - @Test - public void onLoginLoad_withEmptySessionConfiguration_shouldReturnErrorResultIntent() { - ActivityController controller = Robolectric.buildActivity(LoginActivity.class); - controller.create(); - - ShadowActivity shadowActivity = shadowOf(controller.get()); - - assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); - - assertThat(shadowActivity.getResultIntent()).isNotNull(); - - assertThat(getScopeFromIntent(shadowActivity.getResultIntent())) - .isEqualTo(AuthenticationError.UNAVAILABLE); - assertThat(shadowActivity.isFinishing()).isTrue(); - } - - @Test - public void onLoginLoad_withEmptyScopes_shouldReturnErrorResultIntent() { - - Intent intent = new Intent(); - intent.putExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION, new SessionConfiguration.Builder().setClientId("clientId").build()); - - ActivityController controller = Robolectric.buildActivity(LoginActivity.class) - .withIntent(intent) - .create(); - - ShadowActivity shadowActivity = shadowOf(controller.get()); - - assertThat(shadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED); - - assertThat(shadowActivity.getResultIntent()).isNotNull(); - - assertThat(getScopeFromIntent(shadowActivity.getResultIntent())) - .isEqualTo(AuthenticationError.INVALID_SCOPE); - assertThat(shadowActivity.isFinishing()).isTrue(); - } - - private AuthenticationError getScopeFromIntent(Intent intent) { + private AuthenticationError getErrorFromIntent(Intent intent) { return AuthenticationError.fromString(intent.getStringExtra(LoginManager.EXTRA_ERROR)); } } From fc10e1b7afdf2ae03e0484a2312f252cc2cedcd7 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Thu, 6 Sep 2018 11:48:09 -0400 Subject: [PATCH 094/165] Update LoginManager to start LoginActivity with new params --- .../sdk/android/core/auth/LoginManager.java | 47 ++-- .../android/core/auth/LoginManagerTest.java | 262 ++++++------------ 2 files changed, 114 insertions(+), 195 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 02222e84..a207b076 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.util.Log; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.SupportedAppType; @@ -89,9 +90,6 @@ public class LoginManager { static final int REQUEST_CODE_LOGIN_DEFAULT = 1001; - private static final String USER_AGENT = String.format("core-android-v%s-login_manager", - BuildConfig.VERSION_NAME); - private final AccessTokenStorage accessTokenStorage; private final LoginCallback callback; private final SessionConfiguration sessionConfiguration; @@ -175,22 +173,20 @@ public void login(final @NonNull Activity activity) { return; } - SsoDeeplink ssoDeeplink = new SsoDeeplink.Builder(activity) - .clientId(sessionConfiguration.getClientId()) - .scopes(sessionConfiguration.getScopes()) - .customScopes(sessionConfiguration.getCustomScopes()) - .productFlowPriority(productFlowPriority) - .activityRequestCode(requestCode) - .build(); + SsoDeeplink ssoDeeplink = getSsoDeeplink(activity); - if (ssoDeeplink.isSupported()) { - ssoDeeplink.execute(); + if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)) { + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN, + false, true, true); + activity.startActivityForResult(intent, requestCode); + } else if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.DEFAULT)) { + ssoDeeplink.execute(SsoDeeplink.FlowVersion.DEFAULT); } else if (isAuthCodeFlowEnabled()) { loginForAuthorizationCode(activity); - } else if (!AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes())) { - loginForImplicitGrant(activity); } else { - redirectToInstallApp(activity); + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN, + legacyUriRedirectHandler.isLegacyMode(), false, true); + activity.startActivityForResult(intent, requestCode); } } @@ -199,6 +195,7 @@ public void login(final @NonNull Activity activity) { * * @param activity to start Activity on. */ + @Deprecated public void loginForImplicitGrant(@NonNull Activity activity) { if (!legacyUriRedirectHandler.checkValidState(activity, this)) { @@ -365,10 +362,6 @@ public boolean isAuthCodeFlowEnabled() { return authCodeFlowEnabled; } - private void redirectToInstallApp(@NonNull Activity activity) { - new SignupDeeplink(activity, sessionConfiguration.getClientId(), USER_AGENT).execute(); - } - /** * {@link Activity} result handler to be called from starting {@link Activity}. Stores {@link AccessToken} and * notifies consumer callback of login result. @@ -394,6 +387,22 @@ public void onActivityResult( } } + /** + * Generates the deeplink required to execute the SSO Flow + * @param activity the activity to execute the deeplink intent + * @return the object that executes the deeplink + */ + @VisibleForTesting + SsoDeeplink getSsoDeeplink(@NonNull Activity activity) { + return new SsoDeeplink.Builder(activity).clientId(sessionConfiguration.getClientId()) + .scopes(sessionConfiguration.getScopes()) + .customScopes(sessionConfiguration.getCustomScopes()) + .activityRequestCode(requestCode) + .redirectUri(sessionConfiguration.getRedirectUri()) + .productFlowPriority(productFlowPriority) + .build(); + } + private void handleResultCancelled( @NonNull Activity activity, @Nullable Intent data) {// An error occurred during login diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index d558ab37..b881a577 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -24,15 +24,11 @@ import android.app.Activity; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; +import android.content.pm.*; + +import android.support.annotation.NonNull; import com.google.common.collect.ImmutableList; -import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; -import com.uber.sdk.android.core.SupportedAppType; -import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenAuthenticator; import com.uber.sdk.core.auth.AccessTokenStorage; @@ -43,12 +39,12 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.robolectric.Robolectric; - -import java.util.List; -import static com.uber.sdk.android.core.SupportedAppType.UBER; -import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_FORCE_WEBVIEW; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_RESPONSE_TYPE; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_SESSION_CONFIGURATION; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_SSO_ENABLED; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ACCESS_TOKEN; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_CODE_RECEIVED; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ERROR; @@ -57,11 +53,12 @@ import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_SCOPE; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_TOKEN_TYPE; import static com.uber.sdk.android.core.auth.LoginManager.REQUEST_CODE_LOGIN_DEFAULT; +import static com.uber.sdk.android.core.auth.SsoDeeplink.FlowVersion.DEFAULT; +import static com.uber.sdk.android.core.auth.SsoDeeplink.FlowVersion.REDIRECT_TO_SDK; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; @@ -73,37 +70,17 @@ import static org.mockito.Mockito.when; public class LoginManagerTest extends RobolectricTestBase { - private static final String PUBLIC_SIGNATURE = - "3082022730820190a00302010202044cb88a8e300d06092a864886f70d01010505003057311330110603550408130a43616c696" + - "66f726e6961311630140603550407130d53616e204672616e636973636f3110300e060355040a130755626572436162" + - "311630140603550403130d4a6f7264616e20426f6e6e65743020170d3130313031353137303833305a180f323036303" + - "13030323137303833305a3057311330110603550408130a43616c69666f726e6961311630140603550407130d53616e" + - "204672616e636973636f3110300e060355040a130755626572436162311630140603550403130d4a6f7264616e20426" + - "f6e6e657430819f300d06092a864886f70d010101050003818d00308189028181009769b8ee7e4af5eae5bfbac410a0" + - "b0daf8d58ca8c9503878cbfb9461d617b2a5695a639962492ee7f5938f036c7927e4e1a680f186d98fdebf38955fb3f" + - "c23077bd3ff39551cdb35690fd451411c643b26f31d280dc4a55b501e9a0d53d8f8f72a407854516f0f2a4e4d48c02b" + - "dfae408d162a5da34397f845ddfa17de57cd3d0203010001300d06092a864886f70d010105050003818100283f752dc" + - "67c2d8ea2a7e47b1269b2cb37f961c53db3d1c9158af0722978f6a3c396149447557fcf63caa497a795514922f3a4e8" + - "5990608c47d90955ce9cc71f93199a5f3c7624cca8fac70ff70b1e4cf9eb887a92f358aa21ba42e0e86bbecf7d030d8" + - "1a383b716f22ac98746f2956e90b96e8f35d298498e55cdbe4d42a762"; - private static final AccessToken ACCESS_TOKEN = new AccessToken(2592000, ImmutableList.of(Scope.PROFILE, Scope.HISTORY), "thisIsAnAccessToken", "refreshToken", "tokenType"); - private static final int REQUEST_CODE = 9321; private static final String CLIENT_ID = "Client1234"; + private static final String REDIRECT_URI = "com.example.uberauth://redirect"; private static final ImmutableList MIXED_SCOPES = ImmutableList.of(Scope.PROFILE, Scope.REQUEST_RECEIPT); private static final ImmutableList GENERAL_SCOPES = ImmutableList.of(Scope.PROFILE, Scope.HISTORY); - private static final String DEFAULT_REGION = - "uber://connect?client_id=Client1234&scope=profile%20request_receipt&sdk=android&sdk_version=" - + BuildConfig.VERSION_NAME; - - private static final String INSTALL = - String.format("https://m.uber.com/sign-up?client_id=Client1234&user-agent=core-android-v%s-login_manager", - BuildConfig.VERSION_NAME); private static final String AUTHORIZATION_CODE = "Auth123Code"; + private static final String PACKAGE_NAME = "com.example"; @Mock Activity activity; @@ -120,6 +97,9 @@ public class LoginManagerTest extends RobolectricTestBase { @Mock LegacyUriRedirectHandler legacyUriRedirectHandler; + @Mock + SsoDeeplink ssoDeeplink; + SessionConfiguration sessionConfiguration; private LoginManager loginManager; @@ -127,201 +107,140 @@ public class LoginManagerTest extends RobolectricTestBase { @Before public void setup() { sessionConfiguration = new SessionConfiguration.Builder().setClientId(CLIENT_ID) - .setRedirectUri("com.example.uberauth://redirect") + .setRedirectUri(REDIRECT_URI) .setScopes(MIXED_SCOPES).build(); - loginManager = new LoginManager(accessTokenStorage, callback, + loginManager = spy(new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE_LOGIN_DEFAULT, - legacyUriRedirectHandler); + legacyUriRedirectHandler)); + when(loginManager.getSsoDeeplink(any(Activity.class))).thenReturn(ssoDeeplink); when(activity.getPackageManager()).thenReturn(packageManager); when(activity.getApplicationInfo()).thenReturn(new ApplicationInfo()); - when(activity.getPackageName()).thenReturn("com.example"); + when(activity.getPackageName()).thenReturn(PACKAGE_NAME); + when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(true); } @Test public void login_withLegacyModeBlocking_shouldNotLogin() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(false); loginManager.login(activity); verify(activity, never()).startActivityForResult(any(Intent.class), anyInt()); } - @Test - public void login_withLegacyModeNotBlocking_shouldLogin() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); - when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(true); - loginManager.login(activity); - - verify(activity).startActivityForResult(any(Intent.class), anyInt()); - } @Test - public void loginWithAppInstalledPrivilegedScopes_shouldLaunchIntent() { - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); - + public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoParams() { + when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(true); loginManager.login(activity); + verify(ssoDeeplink).isSupported(REDIRECT_TO_SDK); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(DEFAULT_REGION); + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.TOKEN, false, + true, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @Test - public void loginWithAppInstalledPrivilegedScopesAndRequestCode_shouldLaunchIntent() { - loginManager = new LoginManager(accessTokenStorage, callback, - sessionConfiguration, REQUEST_CODE); - - stubAppInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0], SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); + public void login_withDefaultSsoFlowSupported_shouldExecuteDeeplink() { + when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(false); + when(ssoDeeplink.isSupported(DEFAULT)).thenReturn(true); loginManager.login(activity); - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); - - verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - - assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(DEFAULT_REGION); - assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); + verify(ssoDeeplink).isSupported(DEFAULT); + verify(ssoDeeplink).execute(DEFAULT); } @Test - public void login_withEatsProductPriority_shouldLaunchEats() { - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); - stubAppInstalled(packageManager, "com.ubercab", SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); - stubAppInstalled(packageManager, "com.ubercab.eats", SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED); - List appTypes = ImmutableList.of(UBER_EATS); + public void login_withSsoNotSupported_andAuthCodeFlowEnabled_shouldLoginWithAuthCodeFlowParams() { + when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(false); + when(ssoDeeplink.isSupported(DEFAULT)).thenReturn(false); + loginManager.setAuthCodeFlowEnabled(true); - loginManager.setProductFlowPriority(appTypes).login(activity); + loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - assertThat(intentCaptor.getValue().getPackage()).isEqualTo("com.ubercab.eats"); - assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.CODE, false, + false, false); + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @Test - public void login_withRidesAndEatsProductPriority_shouldLaunchRides() { - loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration, REQUEST_CODE); - stubAppInstalled(packageManager, "com.ubercab", SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED); - stubAppInstalled(packageManager, "com.ubercab.eats", SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED); - List appTypes = ImmutableList.of(UBER, UBER_EATS); + public void login_withSsoNotSupported_andAuthCodeFlowDisabled_shouldLoginWithImplicitGrantParamsAndRedirectToPlayStoreEnabled() { + when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(false); + when(ssoDeeplink.isSupported(DEFAULT)).thenReturn(false); - loginManager.setProductFlowPriority(appTypes).login(activity); + loginManager.login(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - assertThat(intentCaptor.getValue().getPackage()).isEqualTo("com.ubercab"); - assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE); + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.TOKEN, false, + false, true); + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @Test - public void loginWithoutAppInstalledGeneralScopesAndAuthCodeFlowDisabled_shouldLaunchImplicitGrant() { - sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManager = new LoginManager(accessTokenStorage, callback, - sessionConfiguration); + public void loginForImplicitGrant_withLegacyModeBlocking_shouldNotLogin() { + when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(false); + loginManager.loginForImplicitGrant(activity); - stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); + verify(activity, never()).startActivityForResult(any(Intent.class), anyInt()); + } - loginManager.login(activity); + @Test + public void loginForImplicitGrant_withoutLegacyModeBlocking_shouldLoginWithImplicitGrantParamsAndRedirectToPlayStoreDisabled() { + loginManager.loginForImplicitGrant(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.TOKEN, false, + false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); - - final Intent capturedIntent = intentCaptor.getValue(); - final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); - final SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() - .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); - assertThat(responseType).isEqualTo(ResponseType.TOKEN); - assertThat(configuration.getScopes()).containsAll(GENERAL_SCOPES); } @Test - public void loginWithoutAppInstalledGeneralScopesAndAuthCodeFlowEnabled_shouldLaunchAuthCodeFlow() { - sessionConfiguration = sessionConfiguration.newBuilder().setScopes(GENERAL_SCOPES).build(); - loginManager = new LoginManager(accessTokenStorage, callback, - sessionConfiguration); - - stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - - loginManager.setAuthCodeFlowEnabled(true); - loginManager.login(activity); - - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); - - verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); - - assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); + public void loginForAuthorizationCode_withLegacyModeBlocking_shouldNotLogin() { + when(legacyUriRedirectHandler.checkValidState(eq(activity), eq(loginManager))).thenReturn(false); + loginManager.loginForAuthorizationCode(activity); - final Intent capturedIntent = intentCaptor.getValue(); - final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); - final SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() - .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); - assertThat(responseType).isEqualTo(ResponseType.CODE); - assertThat(configuration.getScopes()).containsAll(GENERAL_SCOPES); + verify(activity, never()).startActivityForResult(any(Intent.class), anyInt()); } @Test - public void loginWithoutAppInstalledPrivilegedScopesAndAuthCodeFlowEnabled_shouldLaunchAuthCodeFlow() { - stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - - loginManager.setAuthCodeFlowEnabled(true); - loginManager.login(activity); + public void loginForAuthorizationCode_withoutLegacyModeBlocking_shouldLoginWithAuthCodeFlowParams() { + loginManager.loginForAuthorizationCode(activity); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.CODE, false, + false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); - - final Intent capturedIntent = intentCaptor.getValue(); - final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); - final SessionConfiguration configuration = (SessionConfiguration) intentCaptor.getValue() - .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); - assertThat(responseType).isEqualTo(ResponseType.CODE); - assertThat(configuration.getScopes()).containsAll(MIXED_SCOPES); - } - - @Test - public void loginWithoutAppInstalledPrivilegedScopes_shouldLaunchAppInstall() { - final Activity activity = spy(Robolectric.setupActivity(Activity.class)); - when(activity.getPackageManager()).thenReturn(packageManager); - - sessionConfiguration = sessionConfiguration.newBuilder().build(); - loginManager = new LoginManager(accessTokenStorage, callback, - sessionConfiguration) - .setAuthCodeFlowEnabled(false); - - stubAppNotInstalled(packageManager, AppProtocol.UBER_PACKAGE_NAMES[0]); - - loginManager.login(activity); - - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - - verify(activity).startActivity(intentCaptor.capture()); - - assertThat(intentCaptor.getValue().getData().toString()).isEqualTo(INSTALL); } @Test @@ -414,7 +333,7 @@ public void onActivityResult_whenUnavailableAndPrivilegedScopes_shouldTriggerAut final Intent capturedIntent = intentCaptor.getValue(); final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + .getSerializableExtra(EXTRA_RESPONSE_TYPE); final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); @@ -441,7 +360,7 @@ public void onActivityResult_whenUnavailableAndGeneralScopesWithAuthCodeEnabled_ final Intent capturedIntent = intentCaptor.getValue(); final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + .getSerializableExtra(EXTRA_RESPONSE_TYPE); final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); @@ -480,7 +399,7 @@ public void onActivityResult_whenUnavailableAndGeneralScopes_shouldTriggerImplic final Intent capturedIntent = intentCaptor.getValue(); final ResponseType responseType = (ResponseType) capturedIntent - .getSerializableExtra(LoginActivity.EXTRA_RESPONSE_TYPE); + .getSerializableExtra(EXTRA_RESPONSE_TYPE); final SessionConfiguration loginConfiguration = (SessionConfiguration) capturedIntent .getSerializableExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION); @@ -530,27 +449,18 @@ public void getSession_withoutAccessTokenOrToken_fails() { loginManager.getSession(); } - private static PackageManager stubAppInstalled(PackageManager packageManager, String packageName, int versionCode) { - final PackageInfo packageInfo = new PackageInfo(); - packageInfo.versionCode = versionCode; - packageInfo.signatures = new Signature[]{new Signature(PUBLIC_SIGNATURE)}; - packageInfo.packageName = packageName; - try { - when(packageManager.getPackageInfo(eq(packageName), anyInt())) - .thenReturn(packageInfo); - } catch (PackageManager.NameNotFoundException e) { - fail("Unable to mock Package Manager"); - } - return packageManager; - } - - private static PackageManager stubAppNotInstalled(PackageManager packageManager, String packageName) { - try { - when(packageManager.getPackageInfo(eq(packageName), anyInt())) - .thenThrow(PackageManager.NameNotFoundException.class); - } catch (PackageManager.NameNotFoundException e) { - fail("Unable to mock Package Manager"); - } - return packageManager; + private void validateLoginIntentFields( + @NonNull Intent loginIntent, + @NonNull SessionConfiguration expectedSessionConfiguration, + @NonNull ResponseType expectedResponseType, + boolean expectedForceWebview, + boolean expectedSsoEnabled, + boolean expectedRedirectToPlayStoreEnabled) { + assertThat(loginIntent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION)).isEqualTo(expectedSessionConfiguration); + assertThat(loginIntent.getSerializableExtra(EXTRA_RESPONSE_TYPE)).isEqualTo(expectedResponseType); + assertThat(loginIntent.getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)).isEqualTo(expectedForceWebview); + assertThat(loginIntent.getBooleanExtra(EXTRA_SSO_ENABLED, false)).isEqualTo(expectedSsoEnabled); + assertThat(loginIntent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false)) + .isEqualTo(expectedRedirectToPlayStoreEnabled); } } From ca4c58240a2331181caf0af2ec07c3414aac8219 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Wed, 26 Sep 2018 17:03:06 -0400 Subject: [PATCH 095/165] Use correct default for redirect uri in LoginActivity --- .../main/java/com/uber/sdk/android/core/auth/LoginActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index b4f51cd6..1151160d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -200,7 +200,7 @@ protected void loadUrl() { } String redirectUri = sessionConfiguration.getRedirectUri() != null ? sessionConfiguration - .getRedirectUri() : getApplicationContext().getPackageName() + "uberauth"; + .getRedirectUri() : getApplicationContext().getPackageName().concat(".uberauth://redirect"); if (intent.getBooleanExtra(EXTRA_SSO_ENABLED, false)) { SsoDeeplink ssoDeeplink = ssoDeeplinkFactory.getSsoDeeplink(this, sessionConfiguration); From a3b8553f714f1ca183df9794f08f8897def58d29 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Fri, 28 Sep 2018 15:44:22 -0400 Subject: [PATCH 096/165] Add new method for fallback logic in login if neither auth code flow or sso are supported --- .../sdk/android/core/auth/LoginManager.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index a207b076..530655a9 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -184,9 +184,7 @@ public void login(final @NonNull Activity activity) { } else if (isAuthCodeFlowEnabled()) { loginForAuthorizationCode(activity); } else { - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN, - legacyUriRedirectHandler.isLegacyMode(), false, true); - activity.startActivityForResult(intent, requestCode); + loginForImplicitGrantWithFallback(activity); } } @@ -222,6 +220,22 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { activity.startActivityForResult(intent, requestCode); } + /** + * Login using Implicit Grant, but if requesting privileged scopes, fallback to redirecting the user to the play + * store to install the app. + * + * @param activity to start Activity on. + */ + private void loginForImplicitGrantWithFallback(@NonNull Activity activity) { + if (!legacyUriRedirectHandler.checkValidState(activity, this)) { + return; + } + + Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN, + legacyUriRedirectHandler.isLegacyMode(), false, true); + activity.startActivityForResult(intent, requestCode); + } + /** * @return {@link AccessTokenStorage} that is used. * @deprecated Use {@link LoginManager#getAccessTokenStorage()} From ce64f409a476cf61927e07c4ce9b6056f10fdab7 Mon Sep 17 00:00:00 2001 From: Jake Kaufman Date: Mon, 10 Dec 2018 11:40:23 -0500 Subject: [PATCH 097/165] Make sure product priority gets sent in request that goes through LoginActivity --- .../sdk/android/core/auth/LoginActivity.java | 15 +++++++- .../sdk/android/core/auth/LoginManager.java | 21 ++++++++-- .../android/core/auth/SsoDeeplinkFactory.java | 9 ++++- .../android/core/auth/LoginActivityTest.java | 21 ++++++---- .../android/core/auth/LoginManagerTest.java | 38 +++++++++++-------- 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 1151160d..3c1553ce 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -41,10 +41,15 @@ import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.R; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + /** * {@link android.app.Activity} that shows web view for Uber user authentication and authorization. */ @@ -52,6 +57,7 @@ public class LoginActivity extends Activity { @VisibleForTesting static final String USER_AGENT = String.format("core-android-v%s-login_manager", BuildConfig.VERSION_NAME); + static final String EXTRA_PRODUCT_PRIORITY = "PRODUCT_PRIORITY"; static final String EXTRA_RESPONSE_TYPE = "RESPONSE_TYPE"; static final String EXTRA_SESSION_CONFIGURATION = "SESSION_CONFIGURATION"; static final String EXTRA_FORCE_WEBVIEW = "FORCE_WEBVIEW"; @@ -60,6 +66,7 @@ public class LoginActivity extends Activity { static final String ERROR = "error"; + private ArrayList productPriority; private ResponseType responseType; private SessionConfiguration sessionConfiguration; private boolean authStarted; @@ -107,13 +114,14 @@ public static Intent newIntent( @NonNull ResponseType responseType, boolean forceWebview) { - return newIntent(context, sessionConfiguration, responseType, forceWebview, false, false); + return newIntent(context, new ArrayList(), sessionConfiguration, responseType, forceWebview, false, false); } /** * Create an {@link Intent} to pass to this activity * * @param context the {@link Context} for the intent + * @param productPriority dictates the order of which Uber applications should be used for SSO. * @param sessionConfiguration to be used for gather clientId * @param responseType that is expected * @param forceWebview Forced to use old webview instead of chrometabs @@ -123,6 +131,7 @@ public static Intent newIntent( @NonNull static Intent newIntent( @NonNull Context context, + @NonNull ArrayList productPriority, @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType, boolean forceWebview, @@ -130,6 +139,7 @@ static Intent newIntent( boolean isRedirectToPlayStoreEnabled) { final Intent data = new Intent(context, LoginActivity.class) + .putExtra(EXTRA_PRODUCT_PRIORITY, productPriority) .putExtra(EXTRA_SESSION_CONFIGURATION, sessionConfiguration) .putExtra(EXTRA_RESPONSE_TYPE, responseType) .putExtra(EXTRA_FORCE_WEBVIEW, forceWebview) @@ -194,6 +204,7 @@ protected void loadUrl() { sessionConfiguration = (SessionConfiguration) intent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION); responseType = (ResponseType) intent.getSerializableExtra(EXTRA_RESPONSE_TYPE); + productPriority = (ArrayList) intent.getSerializableExtra(EXTRA_PRODUCT_PRIORITY); if (!validateRequestParams()) { return; @@ -203,7 +214,7 @@ protected void loadUrl() { .getRedirectUri() : getApplicationContext().getPackageName().concat(".uberauth://redirect"); if (intent.getBooleanExtra(EXTRA_SSO_ENABLED, false)) { - SsoDeeplink ssoDeeplink = ssoDeeplinkFactory.getSsoDeeplink(this, sessionConfiguration); + SsoDeeplink ssoDeeplink = ssoDeeplinkFactory.getSsoDeeplink(this, productPriority, sessionConfiguration); if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)) { ssoDeeplink.execute(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 530655a9..488bb815 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -41,6 +41,7 @@ import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; +import java.util.ArrayList; import java.util.Collection; import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; @@ -176,8 +177,14 @@ public void login(final @NonNull Activity activity) { SsoDeeplink ssoDeeplink = getSsoDeeplink(activity); if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)) { - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN, - false, true, true); + Intent intent = LoginActivity.newIntent( + activity, + new ArrayList<>(productFlowPriority), + sessionConfiguration, + ResponseType.TOKEN, + false, + true, + true); activity.startActivityForResult(intent, requestCode); } else if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.DEFAULT)) { ssoDeeplink.execute(SsoDeeplink.FlowVersion.DEFAULT); @@ -231,8 +238,14 @@ private void loginForImplicitGrantWithFallback(@NonNull Activity activity) { return; } - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, ResponseType.TOKEN, - legacyUriRedirectHandler.isLegacyMode(), false, true); + Intent intent = LoginActivity.newIntent( + activity, + new ArrayList(), + sessionConfiguration, + ResponseType.TOKEN, + legacyUriRedirectHandler.isLegacyMode(), + false, + true); activity.startActivityForResult(intent, requestCode); } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java index ca3572d5..1d87a9d0 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplinkFactory.java @@ -1,16 +1,23 @@ package com.uber.sdk.android.core.auth; import android.app.Activity; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.core.client.SessionConfiguration; +import java.util.ArrayList; + public class SsoDeeplinkFactory { - SsoDeeplink getSsoDeeplink(Activity activity, SessionConfiguration sessionConfiguration) { + SsoDeeplink getSsoDeeplink( + Activity activity, + ArrayList productPriority, + SessionConfiguration sessionConfiguration) { return new SsoDeeplink.Builder(activity) .clientId(sessionConfiguration.getClientId()) .scopes(sessionConfiguration.getScopes()) .customScopes(sessionConfiguration.getCustomScopes()) .redirectUri(sessionConfiguration.getRedirectUri()) + .productFlowPriority(productPriority) .build(); } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index eb117579..888f7d92 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -27,8 +27,10 @@ import android.net.Uri; import android.support.customtabs.CustomTabsIntent; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; @@ -43,8 +45,11 @@ import org.robolectric.shadows.ShadowWebView; import org.robolectric.util.ActivityController; +import java.util.ArrayList; import java.util.Set; +import static com.uber.sdk.android.core.SupportedAppType.UBER; +import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.assertj.core.api.Assertions.assertThat; @@ -64,6 +69,7 @@ public class LoginActivityTest extends RobolectricTestBase { private static final Set MIXED_SCOPES = Sets.newHashSet(Scope.REQUEST, Scope.PROFILE, Scope.PAYMENT_METHODS); private static final String SIGNUP_DEEPLINK_URL = "https://m.uber.com/sign-up?client_id=" + CLIENT_ID + "&user-agent=" + LoginActivity.USER_AGENT; + private final ArrayList productPriority = new ArrayList<>(ImmutableList.of(UBER_EATS, UBER)); private LoginActivity loginActivity; private SessionConfiguration loginConfiguration; @@ -87,7 +93,8 @@ public void setup() { Intent data = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN); loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(data).get(); - when(ssoDeeplinkFactory.getSsoDeeplink(any(LoginActivity.class), any(SessionConfiguration.class))).thenReturn(ssoDeeplink); + when(ssoDeeplinkFactory.getSsoDeeplink(any(LoginActivity.class), + eq(productPriority), any(SessionConfiguration.class))).thenReturn(ssoDeeplink); } @Test @@ -143,8 +150,8 @@ public void onLoginLoad_withNullResponseType_shouldReturnErrorResultIntent() { @Test public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, false, true, true); + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, + loginConfiguration, ResponseType.TOKEN, false, true, true); ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); loginActivity = controller.get(); @@ -159,8 +166,8 @@ public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { @Test public void onLoginLoad_withSsoEnabled_andNotSupported_shouldReturnErrorResultIntent() { - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, false, true, true); + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, + loginConfiguration, ResponseType.TOKEN, false, true, true); ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); loginActivity = controller.get(); @@ -275,8 +282,8 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP .setRedirectUri(REDIRECT_URI) .setScopes(MIXED_SCOPES) .build(); - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, true, false, true); + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), + new ArrayList(), loginConfiguration, ResponseType.TOKEN, true, false, true); ShadowActivity shadowActivity = shadowOf(Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get()); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index b881a577..bbee1fd2 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -29,6 +29,7 @@ import android.support.annotation.NonNull; import com.google.common.collect.ImmutableList; import com.uber.sdk.android.core.RobolectricTestBase; +import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenAuthenticator; import com.uber.sdk.core.auth.AccessTokenStorage; @@ -40,11 +41,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_FORCE_WEBVIEW; -import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_RESPONSE_TYPE; -import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_SESSION_CONFIGURATION; -import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_SSO_ENABLED; -import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED; +import java.util.ArrayList; +import java.util.List; + +import static com.uber.sdk.android.core.SupportedAppType.UBER; +import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; +import static com.uber.sdk.android.core.auth.LoginActivity.*; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ACCESS_TOKEN; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_CODE_RECEIVED; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ERROR; @@ -134,6 +136,9 @@ public void login_withLegacyModeBlocking_shouldNotLogin() { @Test public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoParams() { when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(true); + + List productPriority = ImmutableList.of(UBER_EATS, UBER); + loginManager.setProductFlowPriority(productPriority); loginManager.login(activity); verify(ssoDeeplink).isSupported(REDIRECT_TO_SDK); @@ -144,8 +149,8 @@ public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoPa verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.TOKEN, false, - true, true); + validateLoginIntentFields(resultIntent, new ArrayList(productPriority), sessionConfiguration, + ResponseType.TOKEN, false, true, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -174,8 +179,8 @@ public void login_withSsoNotSupported_andAuthCodeFlowEnabled_shouldLoginWithAuth verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.CODE, false, - false, false); + validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, + ResponseType.CODE, false, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -192,8 +197,8 @@ public void login_withSsoNotSupported_andAuthCodeFlowDisabled_shouldLoginWithImp verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.TOKEN, false, - false, true); + validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, + ResponseType.TOKEN, false, false, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -215,8 +220,8 @@ public void loginForImplicitGrant_withoutLegacyModeBlocking_shouldLoginWithImpli verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.TOKEN, false, - false, false); + validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, + ResponseType.TOKEN, false, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -238,8 +243,8 @@ public void loginForAuthorizationCode_withoutLegacyModeBlocking_shouldLoginWithA verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, sessionConfiguration, ResponseType.CODE, false, - false, false); + validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, + ResponseType.CODE, false, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -451,6 +456,7 @@ public void getSession_withoutAccessTokenOrToken_fails() { private void validateLoginIntentFields( @NonNull Intent loginIntent, + @NonNull ArrayList expectedProductPriority, @NonNull SessionConfiguration expectedSessionConfiguration, @NonNull ResponseType expectedResponseType, boolean expectedForceWebview, @@ -458,6 +464,8 @@ private void validateLoginIntentFields( boolean expectedRedirectToPlayStoreEnabled) { assertThat(loginIntent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION)).isEqualTo(expectedSessionConfiguration); assertThat(loginIntent.getSerializableExtra(EXTRA_RESPONSE_TYPE)).isEqualTo(expectedResponseType); + assertThat((ArrayList) loginIntent.getSerializableExtra(EXTRA_PRODUCT_PRIORITY)) + .hasSameElementsAs(expectedProductPriority); assertThat(loginIntent.getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)).isEqualTo(expectedForceWebview); assertThat(loginIntent.getBooleanExtra(EXTRA_SSO_ENABLED, false)).isEqualTo(expectedSsoEnabled); assertThat(loginIntent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false)) From f3fc7874e0888ac8388002746840d0fd09270287 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 14 Dec 2018 12:30:35 -0800 Subject: [PATCH 098/165] Added changelog for next version with eats sso --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a238c60..b5ae9618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ v0.9.2 - TBD ------------ +### Added + - [Issue #144](https://github.com/uber/rides-android-sdk/issues/144) Allow SSO Client to dictate which Uber Apps can be used for SSO + - [Issue #138](https://github.com/uber/rides-android-sdk/issues/138) Support for IETF RFC 8252 + - [Issue #130](https://github.com/uber/rides-android-sdk/issues/130) Support for Uber Eats SSO + +### Fixed + - [Issue #129](https://github.com/uber/rides-android-sdk/issues/129) Allow use of refresh token for non-privileged scopes + - [Issue #119](https://github.com/uber/rides-android-sdk/issues/119) Redirect URL documentation issue + v0.9.1 - 03/20/2018 ------------ From ca1de3146aba511fa3c99346d153e1d9b00ff023 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 14 Dec 2018 13:34:53 -0800 Subject: [PATCH 099/165] [Gradle Release Plugin] - pre tag commit: 'v0.10.0'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ae9618..cd123d83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.9.2 - TBD +v0.10.0 - 12/14/2018 ------------ ### Added diff --git a/gradle.properties b/gradle.properties index 6783f521..ac211a19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Tue, 20 Mar 2018 11:16:52 -0700 +#Fri, 14 Dec 2018 13:33:24 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.9.2-SNAPSHOT -VERSION_NAME=0.9.2-SNAPSHOT +version=0.10.0 +VERSION_NAME=0.10.0 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From d9974f5a89c5d32d947e51203ee32fca7d57e3d3 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Fri, 14 Dec 2018 13:35:12 -0800 Subject: [PATCH 100/165] [Gradle Release Plugin] - new version commit: 'v0.10.1-SNAPSHOT'. --- CHANGELOG.md | 3 +++ gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd123d83..1b2517ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.10.1 - TBD +------------- + v0.10.0 - 12/14/2018 ------------ diff --git a/gradle.properties b/gradle.properties index ac211a19..6d5c1872 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Fri, 14 Dec 2018 13:33:24 -0800 +#Fri, 14 Dec 2018 13:35:12 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.0 -VERSION_NAME=0.10.0 +version=0.10.1-SNAPSHOT +VERSION_NAME=0.10.1-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 42684dbd6ef318a580f785ab4f823aa4ffef8025 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Fri, 25 Jan 2019 13:26:41 -0500 Subject: [PATCH 101/165] Loosening requirement of Scopes for Login. Scopes or CustomScopes is now allowed. --- .../sdk/android/core/auth/LoginManager.java | 14 +++--- .../sdk/android/core/auth/SsoDeeplink.java | 7 +-- .../android/core/auth/LoginManagerTest.java | 45 +++++++++++++++++-- .../android/core/auth/SsoDeeplinkTest.java | 41 +++++++++++++---- 4 files changed, 84 insertions(+), 23 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 488bb815..14c9b8d0 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -28,10 +28,8 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Log; -import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.UberSdk; -import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; @@ -44,7 +42,7 @@ import java.util.ArrayList; import java.util.Collection; -import static com.uber.sdk.core.client.utils.Preconditions.checkNotEmpty; +import static com.uber.sdk.android.core.utils.Preconditions.checkState; import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; /** @@ -165,10 +163,11 @@ public LoginManager( * @param activity the activity used to start the {@link LoginActivity}. */ public void login(final @NonNull Activity activity) { - checkNotEmpty(sessionConfiguration.getScopes(), "Scopes must be set in the Session " + - "Configuration."); - checkNotNull(sessionConfiguration.getRedirectUri(), "Redirect URI must be set in " - + "Session Configuration."); + boolean hasScopes = (sessionConfiguration.getScopes() != null && !sessionConfiguration.getScopes().isEmpty()) + || (sessionConfiguration.getCustomScopes() != null && !sessionConfiguration.getCustomScopes().isEmpty()); + checkState(hasScopes, "Scopes must be set in the Session Configuration."); + checkNotNull(sessionConfiguration.getRedirectUri(), + "Redirect URI must be set in Session Configuration."); if (!legacyUriRedirectHandler.checkValidState(activity, this)) { return; @@ -416,6 +415,7 @@ public void onActivityResult( /** * Generates the deeplink required to execute the SSO Flow + * * @param activity the activity to execute the deeplink intent * @return the object that executes the deeplink */ diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 5fcb8e9f..58155f2d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -45,7 +45,6 @@ import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; import static com.uber.sdk.android.core.UberSdk.UBER_SDK_LOG_TAG; -import static com.uber.sdk.android.core.utils.Preconditions.checkNotEmpty; import static com.uber.sdk.android.core.utils.Preconditions.checkNotNull; import static com.uber.sdk.android.core.utils.Preconditions.checkState; @@ -117,7 +116,7 @@ public void execute() { * * @param flowVersion specifies which client implementation to use for handling the response to the SSO request * @throws IllegalStateException if compatible Uber app is not installed or the deeplink is incorrectly configured. - * Use {@link #isSupported()} to check. + * Use {@link #isSupported()} to check. */ void execute(@NonNull FlowVersion flowVersion) { checkState(isSupported(flowVersion), "Single sign on is not supported on the device. " + @@ -277,7 +276,9 @@ Builder redirectUri(@NonNull String redirectUri) { public SsoDeeplink build() { checkNotNull(clientId, "Client Id must be set"); - checkNotEmpty(requestedScopes, "Scopes must be set."); + boolean hasScopes = (requestedScopes != null && !requestedScopes.isEmpty()) + || (requestedCustomScopes != null && !requestedCustomScopes.isEmpty()); + checkState(hasScopes, "Scopes must be set."); if (requestedCustomScopes == null) { requestedCustomScopes = new ArrayList<>(); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index bbee1fd2..8048d378 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -24,8 +24,8 @@ import android.app.Activity; import android.content.Intent; -import android.content.pm.*; - +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.support.annotation.NonNull; import com.google.common.collect.ImmutableList; import com.uber.sdk.android.core.RobolectricTestBase; @@ -46,7 +46,12 @@ import static com.uber.sdk.android.core.SupportedAppType.UBER; import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; -import static com.uber.sdk.android.core.auth.LoginActivity.*; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_FORCE_WEBVIEW; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_PRODUCT_PRIORITY; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_RESPONSE_TYPE; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_SESSION_CONFIGURATION; +import static com.uber.sdk.android.core.auth.LoginActivity.EXTRA_SSO_ENABLED; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ACCESS_TOKEN; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_CODE_RECEIVED; import static com.uber.sdk.android.core.auth.LoginManager.EXTRA_ERROR; @@ -80,6 +85,8 @@ public class LoginManagerTest extends RobolectricTestBase { private static final String REDIRECT_URI = "com.example.uberauth://redirect"; private static final ImmutableList MIXED_SCOPES = ImmutableList.of(Scope.PROFILE, Scope.REQUEST_RECEIPT); private static final ImmutableList GENERAL_SCOPES = ImmutableList.of(Scope.PROFILE, Scope.HISTORY); + private static final ImmutableList EMPTY_SCOPES = ImmutableList.of(); + private static final ImmutableList CUSTOM_SCOPES = ImmutableList.of("foo", "bar"); private static final String AUTHORIZATION_CODE = "Auth123Code"; private static final String PACKAGE_NAME = "com.example"; @@ -132,7 +139,6 @@ public void login_withLegacyModeBlocking_shouldNotLogin() { verify(activity, never()).startActivityForResult(any(Intent.class), anyInt()); } - @Test public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoParams() { when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(true); @@ -248,6 +254,37 @@ public void loginForAuthorizationCode_withoutLegacyModeBlocking_shouldLoginWithA assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } + @Test(expected = IllegalStateException.class) + public void login_whenMissingScopes_shouldThrowException() { + sessionConfiguration = sessionConfiguration.newBuilder().setScopes(EMPTY_SCOPES).build(); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); + + loginManager.setAuthCodeFlowEnabled(true); + loginManager.login(activity); + } + + @Test + public void login_whenOnlyCustomScopes_shouldLogin() { + sessionConfiguration = sessionConfiguration.newBuilder() + .setScopes(EMPTY_SCOPES) + .setCustomScopes(CUSTOM_SCOPES) + .build(); + loginManager = new LoginManager(accessTokenStorage, callback, sessionConfiguration); + + loginManager.setAuthCodeFlowEnabled(true); + loginManager.login(activity); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, + ResponseType.CODE, false, false, false); + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); + } + @Test public void onActivityResult_whenResultOkAndHasToken_shouldCallbackSuccess() { Intent intent = new Intent() diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 862b5ec7..4ad5a378 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -33,7 +33,6 @@ import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.android.core.utils.AppProtocol; - import com.uber.sdk.core.auth.Scope; import org.junit.Before; import org.junit.Test; @@ -46,7 +45,6 @@ import org.robolectric.shadows.ShadowResolveInfo; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Set; @@ -55,13 +53,18 @@ import static com.uber.sdk.android.core.auth.SsoDeeplink.FlowVersion.DEFAULT; import static com.uber.sdk.android.core.auth.SsoDeeplink.FlowVersion.REDIRECT_TO_SDK; import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_EATS_VERSION_SUPPORTED; -import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED; import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED; +import static com.uber.sdk.android.core.auth.SsoDeeplink.MIN_UBER_RIDES_VERSION_SUPPORTED; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class SsoDeeplinkTest extends RobolectricTestBase { @@ -438,13 +441,11 @@ public void execute_withoutRequestCode_shouldUseDefaultRequestCode() { public void execute_withScopesAndCustomScopes_shouldSucceed() { enableSupport(DEFAULT); - Collection collection = Arrays.asList("sample", "test"); - new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) .activityRequestCode(REQUEST_CODE) .scopes(GENERAL_SCOPES) - .customScopes(collection) + .customScopes(Arrays.asList("sample", "test")) .appProtocol(appProtocol) .build() .execute(); @@ -456,6 +457,26 @@ public void execute_withScopesAndCustomScopes_shouldSucceed() { assertThat(uri.getQueryParameter("scope")).contains("history", "profile", "sample", "test"); } + @Test + public void execute_withOnlyCustomScopes_shouldSucceed() { + enableSupport(DEFAULT); + + new SsoDeeplink.Builder(activity) + .clientId(CLIENT_ID) + .activityRequestCode(REQUEST_CODE) + .scopes(Collections.emptyList()) + .customScopes(Arrays.asList("sample", "test")) + .appProtocol(appProtocol) + .build() + .execute(); + + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); + + Uri uri = intentArgumentCaptor.getValue().getData(); + assertThat(uri.getQueryParameter("scope")).contains("sample", "test"); + } + @Test public void execute_withoutRedirectUri_shouldUseDefaultUri() { enableSupport(REDIRECT_TO_SDK); @@ -480,11 +501,13 @@ public void execute_withoutRedirectUri_shouldUseDefaultUri() { } @Test(expected = IllegalStateException.class) - public void execute_withoutScopes_shouldFail() { + public void execute_withoutAnyScopes_shouldFail() { enableSupport(DEFAULT); new SsoDeeplink.Builder(activity) .clientId(CLIENT_ID) + .scopes(Collections.emptyList()) + .customScopes(Collections.emptyList()) .activityRequestCode(REQUEST_CODE) .appProtocol(appProtocol) .build() From ccbf0a031057ba157c45162fd4abf407878dc959 Mon Sep 17 00:00:00 2001 From: William Vanderhoef Date: Fri, 25 Jan 2019 14:23:10 -0500 Subject: [PATCH 102/165] Format travis and remove `-no-audio` option for emulator. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9fb628d8..aca24380 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_install: - sdkmanager "system-images;android-18;default;armeabi-v7a" # Create and start emulator for the script. Meant to race the install task. - echo no | avdmanager create avd --force -n test -k "system-images;android-18;default;armeabi-v7a" - - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window > /dev/null 2>&1 & + - $ANDROID_HOME/emulator/emulator -avd test -no-window > /dev/null 2>&1 & install: ./gradlew clean assemble assembleAndroidTest --stacktrace @@ -32,12 +32,12 @@ script: - ./gradlew check connectedCheck --stacktrace after_success: - - .buildscript/deploy_snapshot.sh + - .buildscript/deploy_snapshot.sh env: global: - - secure: "g5g+VmfqVF7PMuTl00NUQpX4pFohsEVb/SUK3adFWdT5SKOZgi28ArwUEmCMbG/he5UyyQJkv38CKfa+6jZM1cDDNkICGEjM3YCImJyCDpUKLKLFlsvDtAPK7H8rUpLgmGOHQONObtJbhtkmWg+0fr6TRj+yltZZl/6dKZ7Im4aeeMDE03Hy8MubvxRqANF3lT1bJMLUTIAP/gHSD46AKhlbnUJHW5QXqKJqMbtt3nyZNZ3aWPVMoGc+zTbpMWD6dvCH0Etc/nEatMINLunHEUf59CVEiCmQSHrWHsyDn75OiVCJUEzj9euTKE1Kz5OjKrPNYPlW1V840EOLdr/I3rz50gFmMjSyNZcd6D4W6jygOC0QDm57ISHO/Jtl4iLaPzqKMRT4daiyqWZJrnFB8Xlt8CjCxxxXT0A1ZXgro1Auoaa4OVg53Ey40CuRABHbVu6KnskcT5A52XHlDxh3wi5LnPaLH5LzB5/erFzzgLn0GPYTVKInKen6/cY+/+h84rMgS8VkKegl3oUuz1Kzej1GkxnFzdwm3j+1CuDTkadmlpn92+N6wXlG628cs7e93m0QhxXsd+mIaAwyicQeelsOeaAXTp72xfvXE7RQA2YNXjAqVoXu0kTWlXs2aDlm3HHOS4FR0kR96TJarqpinD+zAzEseaP4rMCgqdOsKok=" - - secure: "cp+/y+Zw6ijlK/JBigffDVhQyVhZTfSfgkkM5V0USlJIS2c3N7e1vruiXIcRLEIfzgTbU0veVn37NDIDMkoUDlUY9q2p0uM+xwoO8URYK/SqOhevFSd+QgMX9QwnaV5reD6aZzrz/SI6AbA6hAmIRIXZGkxRfgsB1nJN4m1Fwiwib2IXPHmBa+8d1vS7yU36FrY/3fHthgz1T1M+VDVFmbIGbBqCj5GR6CF+TcDcEQW3UvH671s7IAzuY8VzopeQjaqHE+U+8wrkg0mmiX6/B3a9nH6G/rfaeLTpomb0QCojk+W8en85f7KdK6/7SEiKU0NJmJ62ccc8n0h1VxhWYx/r2S2nVM8FfVuOTOPWuGZU0ULI1ylqAYgppBMp49eOlAJV5QzyBB7Q+py20wEPBisIlvZXLytNm7RWojiTt1wm2PaEhFzYmfPgLlViFDv7N+hLhxproDpw+c1XhhM2ZPyxW8oUriRxyMRqXkJClEgNg+0X9IyFzGc93S7yw0u5SMwhC735vjk3G4sSL/bZjre5zSc/XXM+HqIzWP+KUwRZqdcwW7uuGMB8LUWAHiCrZUuCRL25cisKW/xArH3Sg4nrFG3VP+8yMgRRVJWMtCandSzsJcBmiFPEqsb12eTo2hQfJzoKMX9HcOitfkjn1U1zsJm1ycsG61prF7fhdHg=" + - secure: "g5g+VmfqVF7PMuTl00NUQpX4pFohsEVb/SUK3adFWdT5SKOZgi28ArwUEmCMbG/he5UyyQJkv38CKfa+6jZM1cDDNkICGEjM3YCImJyCDpUKLKLFlsvDtAPK7H8rUpLgmGOHQONObtJbhtkmWg+0fr6TRj+yltZZl/6dKZ7Im4aeeMDE03Hy8MubvxRqANF3lT1bJMLUTIAP/gHSD46AKhlbnUJHW5QXqKJqMbtt3nyZNZ3aWPVMoGc+zTbpMWD6dvCH0Etc/nEatMINLunHEUf59CVEiCmQSHrWHsyDn75OiVCJUEzj9euTKE1Kz5OjKrPNYPlW1V840EOLdr/I3rz50gFmMjSyNZcd6D4W6jygOC0QDm57ISHO/Jtl4iLaPzqKMRT4daiyqWZJrnFB8Xlt8CjCxxxXT0A1ZXgro1Auoaa4OVg53Ey40CuRABHbVu6KnskcT5A52XHlDxh3wi5LnPaLH5LzB5/erFzzgLn0GPYTVKInKen6/cY+/+h84rMgS8VkKegl3oUuz1Kzej1GkxnFzdwm3j+1CuDTkadmlpn92+N6wXlG628cs7e93m0QhxXsd+mIaAwyicQeelsOeaAXTp72xfvXE7RQA2YNXjAqVoXu0kTWlXs2aDlm3HHOS4FR0kR96TJarqpinD+zAzEseaP4rMCgqdOsKok=" + - secure: "cp+/y+Zw6ijlK/JBigffDVhQyVhZTfSfgkkM5V0USlJIS2c3N7e1vruiXIcRLEIfzgTbU0veVn37NDIDMkoUDlUY9q2p0uM+xwoO8URYK/SqOhevFSd+QgMX9QwnaV5reD6aZzrz/SI6AbA6hAmIRIXZGkxRfgsB1nJN4m1Fwiwib2IXPHmBa+8d1vS7yU36FrY/3fHthgz1T1M+VDVFmbIGbBqCj5GR6CF+TcDcEQW3UvH671s7IAzuY8VzopeQjaqHE+U+8wrkg0mmiX6/B3a9nH6G/rfaeLTpomb0QCojk+W8en85f7KdK6/7SEiKU0NJmJ62ccc8n0h1VxhWYx/r2S2nVM8FfVuOTOPWuGZU0ULI1ylqAYgppBMp49eOlAJV5QzyBB7Q+py20wEPBisIlvZXLytNm7RWojiTt1wm2PaEhFzYmfPgLlViFDv7N+hLhxproDpw+c1XhhM2ZPyxW8oUriRxyMRqXkJClEgNg+0X9IyFzGc93S7yw0u5SMwhC735vjk3G4sSL/bZjre5zSc/XXM+HqIzWP+KUwRZqdcwW7uuGMB8LUWAHiCrZUuCRL25cisKW/xArH3Sg4nrFG3VP+8yMgRRVJWMtCandSzsJcBmiFPEqsb12eTo2hQfJzoKMX9HcOitfkjn1U1zsJm1ycsG61prF7fhdHg=" branches: except: From bc6d69f6ac795d55392f1332312779ac8f636502 Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Thu, 7 Feb 2019 15:17:47 -0500 Subject: [PATCH 103/165] Fix NPE caused by unset `productFlowPriority` Fixed #153 Verified by unit tests. --- .../sdk/android/core/auth/LoginManager.java | 7 +++--- .../android/core/auth/LoginManagerTest.java | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 14c9b8d0..e495928b 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -95,7 +95,7 @@ public class LoginManager { private final int requestCode; private final LegacyUriRedirectHandler legacyUriRedirectHandler; - private Collection productFlowPriority; + private ArrayList productFlowPriority; private boolean authCodeFlowEnabled = false; @Deprecated private boolean redirectForAuthorizationCode = false; @@ -152,6 +152,7 @@ public LoginManager( @NonNull LegacyUriRedirectHandler legacyUriRedirectHandler) { this.accessTokenStorage = accessTokenStorage; this.callback = loginCallback; + this.productFlowPriority = new ArrayList<>(); this.sessionConfiguration = configuration; this.requestCode = requestCode; this.legacyUriRedirectHandler = legacyUriRedirectHandler; @@ -178,7 +179,7 @@ public void login(final @NonNull Activity activity) { if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.REDIRECT_TO_SDK)) { Intent intent = LoginActivity.newIntent( activity, - new ArrayList<>(productFlowPriority), + productFlowPriority, sessionConfiguration, ResponseType.TOKEN, false, @@ -372,7 +373,7 @@ public LoginManager setAuthCodeFlowEnabled(boolean authCodeFlowEnabled) { * @return this instance of {@link LoginManager}. */ public LoginManager setProductFlowPriority(@NonNull Collection productFlowPriority) { - this.productFlowPriority = productFlowPriority; + this.productFlowPriority = new ArrayList<>(productFlowPriority); return this; } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 8048d378..f0e965d9 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -36,6 +36,7 @@ import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; +import edu.emory.mathcs.backport.java.util.Collections; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -160,6 +161,27 @@ public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoPa assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } + @Test + public void login_withoutAppPriority_shouldLoginActivityWithSsoParams() { + when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(true); + ArrayList supportedAppTypes = new ArrayList<>(); + supportedAppTypes.add(UBER); + + loginManager.login(activity); + + verify(ssoDeeplink).isSupported(REDIRECT_TO_SDK); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor codeCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); + + final Intent resultIntent = intentCaptor.getValue(); + validateLoginIntentFields(resultIntent, supportedAppTypes, sessionConfiguration, + ResponseType.TOKEN, false, true, true); + assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); + } + @Test public void login_withDefaultSsoFlowSupported_shouldExecuteDeeplink() { when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(false); @@ -493,7 +515,7 @@ public void getSession_withoutAccessTokenOrToken_fails() { private void validateLoginIntentFields( @NonNull Intent loginIntent, - @NonNull ArrayList expectedProductPriority, + @NonNull Iterable expectedProductPriority, @NonNull SessionConfiguration expectedSessionConfiguration, @NonNull ResponseType expectedResponseType, boolean expectedForceWebview, From 6784fb542f5240e2fbf05d2555679378e4feb0df Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Thu, 7 Feb 2019 16:08:28 -0500 Subject: [PATCH 104/165] Fixing a test --- .../uber/sdk/android/core/auth/LoginManagerTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index f0e965d9..fc2aab3d 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -156,7 +156,7 @@ public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoPa verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, new ArrayList(productPriority), sessionConfiguration, + validateLoginIntentFields(resultIntent, new ArrayList<>(productPriority), sessionConfiguration, ResponseType.TOKEN, false, true, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -164,8 +164,6 @@ public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoPa @Test public void login_withoutAppPriority_shouldLoginActivityWithSsoParams() { when(ssoDeeplink.isSupported(REDIRECT_TO_SDK)).thenReturn(true); - ArrayList supportedAppTypes = new ArrayList<>(); - supportedAppTypes.add(UBER); loginManager.login(activity); @@ -177,7 +175,7 @@ public void login_withoutAppPriority_shouldLoginActivityWithSsoParams() { verify(activity).startActivityForResult(intentCaptor.capture(), codeCaptor.capture()); final Intent resultIntent = intentCaptor.getValue(); - validateLoginIntentFields(resultIntent, supportedAppTypes, sessionConfiguration, + validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, ResponseType.TOKEN, false, true, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -515,7 +513,7 @@ public void getSession_withoutAccessTokenOrToken_fails() { private void validateLoginIntentFields( @NonNull Intent loginIntent, - @NonNull Iterable expectedProductPriority, + @NonNull List expectedProductPriority, @NonNull SessionConfiguration expectedSessionConfiguration, @NonNull ResponseType expectedResponseType, boolean expectedForceWebview, @@ -524,7 +522,7 @@ private void validateLoginIntentFields( assertThat(loginIntent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION)).isEqualTo(expectedSessionConfiguration); assertThat(loginIntent.getSerializableExtra(EXTRA_RESPONSE_TYPE)).isEqualTo(expectedResponseType); assertThat((ArrayList) loginIntent.getSerializableExtra(EXTRA_PRODUCT_PRIORITY)) - .hasSameElementsAs(expectedProductPriority); + .containsAll(expectedProductPriority); assertThat(loginIntent.getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)).isEqualTo(expectedForceWebview); assertThat(loginIntent.getBooleanExtra(EXTRA_SSO_ENABLED, false)).isEqualTo(expectedSsoEnabled); assertThat(loginIntent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false)) From 9878043661e0ba835fd4d6e2f4a45a0eb6839653 Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Wed, 27 Feb 2019 13:54:59 -0500 Subject: [PATCH 105/165] [Gradle Release Plugin] - pre tag commit: 'v0.10.1'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2517ce..6ab4b97e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.10.1 - TBD +v0.10.1 - 02/27/2019 ------------- v0.10.0 - 12/14/2018 diff --git a/gradle.properties b/gradle.properties index 6d5c1872..a1b544eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Fri, 14 Dec 2018 13:35:12 -0800 +#Wed, 27 Feb 2019 13:53:30 -0500 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.1-SNAPSHOT -VERSION_NAME=0.10.1-SNAPSHOT +version=0.10.1 +VERSION_NAME=0.10.1 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 25fc5fa1aa96e48fda0d534cf1e7c6751720e10c Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Wed, 27 Feb 2019 13:55:32 -0500 Subject: [PATCH 106/165] [Gradle Release Plugin] - new version commit: 'v0.10.2-SNAPSHOT'. --- CHANGELOG.md | 3 +++ gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab4b97e..341e4ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.10.2 - TBD +------------- + v0.10.1 - 02/27/2019 ------------- diff --git a/gradle.properties b/gradle.properties index a1b544eb..462c5907 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Wed, 27 Feb 2019 13:53:30 -0500 +#Wed, 27 Feb 2019 13:55:32 -0500 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.1 -VERSION_NAME=0.10.1 +version=0.10.2-SNAPSHOT +VERSION_NAME=0.10.2-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 584b89b88be73e2afd3dc2507c15b1c1726baf85 Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Wed, 20 Feb 2019 10:28:35 -0800 Subject: [PATCH 107/165] Handle redirect for ChromeTab Auth Code flow. fixes #159 --- .../uber/sdk/android/core/auth/AuthUtils.java | 7 +++- .../sdk/android/core/auth/LoginActivity.java | 34 ++++++++++++++----- .../sdk/android/core/auth/AuthUtilsTest.java | 34 ++++++++++++------- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index cc8a6b1e..2f658878 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -49,6 +49,7 @@ * A utility class for the Uber SDK. */ class AuthUtils { + static final String KEY_AUTHENTICATION_CODE = "code"; static final String KEY_EXPIRATION_TIME = "expires_in"; static final String KEY_SCOPES = "scope"; static final String KEY_TOKEN = "access_token"; @@ -222,8 +223,12 @@ static Intent parseTokenUriToIntent(@NonNull Uri uri) throws LoginAuthentication return data; } + static boolean isAuthorizationCodePresent(@NonNull Uri uri) { + return !TextUtils.isEmpty(uri.getQueryParameter(KEY_AUTHENTICATION_CODE)); + } + static String parseAuthorizationCode(@NonNull Uri uri) throws LoginAuthenticationException { - final String code = uri.getQueryParameter("code"); + final String code = uri.getQueryParameter(KEY_AUTHENTICATION_CODE); if (TextUtils.isEmpty(code)) { throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE); } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 3c1553ce..2bdc45ff 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -245,12 +245,29 @@ protected void loadWebPage(String redirectUri, ResponseType responseType, Sessio } } - protected boolean handleResponse(@NonNull Uri uri) { - final String fragment = uri.getFragment(); + /** + * Handler for both AccessToken and AuthorizationCode redirects. + * + * @param uri The redirect Uri. + */ + private void handleResponse(@NonNull Uri uri) { + if (AuthUtils.isAuthorizationCodePresent(uri)) { + onCodeReceived(uri); + } else { + handleAccessTokenResponse(uri); + } + } - if (fragment == null) { + /** + * Process the callback for AccessToken. + * + * @param uri Redirect URI containing AccessToken values. + */ + private void handleAccessTokenResponse(@NonNull Uri uri) { + final String fragment = uri.getFragment(); + if (TextUtils.isEmpty(fragment)) { onError(AuthenticationError.INVALID_RESPONSE); - return true; + return; } final Uri fragmentUri = new Uri.Builder().encodedQuery(fragment).build(); @@ -259,11 +276,9 @@ protected boolean handleResponse(@NonNull Uri uri) { final String error = fragmentUri.getQueryParameter(ERROR); if (!TextUtils.isEmpty(error)) { onError(AuthenticationError.fromString(error)); - return true; + } else { + onTokenReceived(fragmentUri); } - - onTokenReceived(fragmentUri); - return true; } protected void loadWebview(String url, String redirectUri) { @@ -427,7 +442,8 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { } if (url.startsWith(redirectUri)) { - return handleResponse(uri); + handleAccessTokenResponse(uri); + return true; } return super.shouldOverrideUrlLoading(view, url); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index 550e3468..4c7ded39 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -35,10 +35,7 @@ import java.util.Arrays; import java.util.List; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; +import static junit.framework.Assert.*; import static org.assertj.core.api.Assertions.assertThat; public class AuthUtilsTest extends RobolectricTestBase { @@ -47,7 +44,7 @@ public class AuthUtilsTest extends RobolectricTestBase { private static final String BEARER = "Bearer"; private final String ACCESS_TOKEN_STRING = "accessToken1234"; - private final long EXPIRATION_TIME = 1458770906206l; + private final long EXPIRATION_TIME = 1458770906206L; @Test public void stringToScopeCollection_whenOneScopeInString_shouldReturnCollectionOfOneScope() { @@ -237,6 +234,23 @@ public void generateAccessTokenFromUrl_whenValidAccessTokenWithMultipleScopes_sh assertThat(accessToken.getTokenType()).isEqualTo(BEARER); } + @Test + public void isAuthorizationCodePresent_whenPresent_shouldReturnTrue() { + String redirectUrl = "http://localhost:1234?code=" + AUTH_CODE; + + assertTrue(AuthUtils.isAuthorizationCodePresent(Uri.parse(redirectUrl))); + } + + @Test + public void isAuthorizationCodePresent_whenEmpty_shouldReturnFalse() { + assertFalse(AuthUtils.isAuthorizationCodePresent(Uri.parse("http://localhost:1234?code="))); + } + + @Test + public void isAuthorizationCodePresent_whenMissing_shouldReturnFalse() { + assertFalse(AuthUtils.isAuthorizationCodePresent(Uri.parse("http://localhost:1234"))); + } + @Test public void getCodeFromUrl_whenValidAuthorizationCodePassed() throws LoginAuthenticationException { String redirectUrl = "http://localhost:1234?code=" + AUTH_CODE; @@ -244,18 +258,12 @@ public void getCodeFromUrl_whenValidAuthorizationCodePassed() throws LoginAuthen assertThat(AuthUtils.parseAuthorizationCode(Uri.parse(redirectUrl))).isEqualTo(AUTH_CODE); } - @Test + @Test(expected = LoginAuthenticationException.class) public void getCodeFromUrl_whenNoValidAuthorizationCodePassed() throws LoginAuthenticationException { String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&expires_in=" + EXPIRATION_TIME + "&scope=history"; - - try { - AuthUtils.parseAuthorizationCode(Uri.parse(redirectUrl)); - fail("Should throw an exception"); - } catch (LoginAuthenticationException e) { - assertThat(e.getAuthenticationError()).isEqualTo(AuthenticationError.INVALID_RESPONSE); - } + AuthUtils.parseAuthorizationCode(Uri.parse(redirectUrl)); } @Test From 45ff7c711c6a5c33c7aa6bfe932025190cbc9c62 Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Fri, 1 Mar 2019 16:35:40 -0500 Subject: [PATCH 108/165] Addressing feedback on AuthUtilsTest --- .../uber/sdk/android/core/auth/AuthUtilsTest.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index 4c7ded39..0f68f4a8 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -23,12 +23,10 @@ package com.uber.sdk.android.core.auth; import android.net.Uri; - import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; - import org.junit.Test; import java.util.ArrayList; @@ -44,6 +42,7 @@ public class AuthUtilsTest extends RobolectricTestBase { private static final String BEARER = "Bearer"; private final String ACCESS_TOKEN_STRING = "accessToken1234"; + // GMT: Wednesday, March 23, 2016 10:08:26 PM private final long EXPIRATION_TIME = 1458770906206L; @Test @@ -258,12 +257,17 @@ public void getCodeFromUrl_whenValidAuthorizationCodePassed() throws LoginAuthen assertThat(AuthUtils.parseAuthorizationCode(Uri.parse(redirectUrl))).isEqualTo(AUTH_CODE); } - @Test(expected = LoginAuthenticationException.class) - public void getCodeFromUrl_whenNoValidAuthorizationCodePassed() throws LoginAuthenticationException { + @Test + public void getCodeFromUrl_whenNoValidAuthorizationCodePassed() { String redirectUrl = "http://localhost:1234?access_token=" + ACCESS_TOKEN_STRING + "&expires_in=" + EXPIRATION_TIME + "&scope=history"; - AuthUtils.parseAuthorizationCode(Uri.parse(redirectUrl)); + try { + AuthUtils.parseAuthorizationCode(Uri.parse(redirectUrl)); + fail("Authorization Code should not be parsable from Access Token response."); + } catch (LoginAuthenticationException e) { + // When an access token string is found when parsing authorization code we expect to get an exception. + } } @Test From 6d7506015afca8d05208d507278622afa5687b9c Mon Sep 17 00:00:00 2001 From: Marvyn Leroy Date: Wed, 6 Mar 2019 17:24:21 -0500 Subject: [PATCH 109/165] Update minimum Uber Eats version to 2488 --- .../main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 58155f2d..57748fb8 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -59,7 +59,7 @@ public class SsoDeeplink implements Deeplink { @VisibleForTesting static final int MIN_UBER_RIDES_VERSION_SUPPORTED = 31302; @VisibleForTesting - static final int MIN_UBER_EATS_VERSION_SUPPORTED = 1085; + static final int MIN_UBER_EATS_VERSION_SUPPORTED = 2488; @VisibleForTesting static final int MIN_UBER_RIDES_VERSION_REDIRECT_FLOW_SUPPORTED = 35757; From bed5d60a2325f5bc710832e36196bd3c38e3666b Mon Sep 17 00:00:00 2001 From: Marvyn Leroy Date: Thu, 7 Mar 2019 11:25:03 -0500 Subject: [PATCH 110/165] Recognize nightly & internal builds --- .../com/uber/sdk/android/core/utils/AppProtocol.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 5f81279d..b391b9b4 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -26,20 +26,19 @@ import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; public class AppProtocol { - @Deprecated - public static final String[] UBER_PACKAGE_NAMES = - {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo", "com.ubercab.presidio.development"}; - @VisibleForTesting static final String[] RIDER_PACKAGE_NAMES = {"com.ubercab.presidio.development", "com.ubercab.presidio.exo", "com.ubercab.presidio.app", "com.ubercab"}; @VisibleForTesting static final String[] EATS_PACKAGE_NAMES = - {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats"}; + {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats", "com.ubercab.eats.internal", "com.ubercab.eats.nightly"}; public static final String PLATFORM = "android"; - private static final String UBER_RIDER_HASH = "411c40b31f6d01dac68d711df99b6eafeec8e73b"; + // Both com.ubercab.eats and com.ubercab.eats.nightly share the same hash private static final String UBER_EATS_HASH = "ae0b86995f174533b423067837beba13d922fbb0"; + // For com.ubercab.eats.internal + private static final String UBER_EATS_INTERNAL_HASH = "9a715f0cbb44b01e3c41c9bda30feba107561e79"; + private static final String UBER_RIDER_HASH = "411c40b31f6d01dac68d711df99b6eafeec8e73b"; private static final String HASH_ALGORITHM_SHA1 = "SHA-1"; private static final int DEFAULT_MIN_VERSION = 0; @@ -50,6 +49,7 @@ private static HashSet buildAppSignatureHashes() { HashSet set = new HashSet<>(); set.add(UBER_RIDER_HASH); set.add(UBER_EATS_HASH); + set.add(UBER_EATS_INTERNAL_HASH); return set; } From 6edb3ac9b817546e0a83372bae3a5a27a376e697 Mon Sep 17 00:00:00 2001 From: Marvyn Leroy Date: Thu, 7 Mar 2019 11:46:26 -0500 Subject: [PATCH 111/165] Bring back deprecated code and change order of package names --- .../java/com/uber/sdk/android/core/utils/AppProtocol.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index b391b9b4..445deec0 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -26,12 +26,16 @@ import static com.uber.sdk.android.core.SupportedAppType.UBER_EATS; public class AppProtocol { + @Deprecated + public static final String[] UBER_PACKAGE_NAMES = + {"com.ubercab", "com.ubercab.presidio.app", "com.ubercab.presidio.exo", "com.ubercab.presidio.development"}; + @VisibleForTesting static final String[] RIDER_PACKAGE_NAMES = {"com.ubercab.presidio.development", "com.ubercab.presidio.exo", "com.ubercab.presidio.app", "com.ubercab"}; @VisibleForTesting static final String[] EATS_PACKAGE_NAMES = - {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats", "com.ubercab.eats.internal", "com.ubercab.eats.nightly"}; + {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats.internal", "com.ubercab.eats.nightly", "com.ubercab.eats"}; public static final String PLATFORM = "android"; // Both com.ubercab.eats and com.ubercab.eats.nightly share the same hash From 950c92e962f605cc5eafc9ee7facd3bcd93aa928 Mon Sep 17 00:00:00 2001 From: Marvyn Leroy Date: Thu, 7 Mar 2019 18:32:11 -0500 Subject: [PATCH 112/165] Introduce compatibility with nightly build --- .../main/java/com/uber/sdk/android/core/utils/AppProtocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 445deec0..77d35f92 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -35,7 +35,7 @@ public class AppProtocol { {"com.ubercab.presidio.development", "com.ubercab.presidio.exo", "com.ubercab.presidio.app", "com.ubercab"}; @VisibleForTesting static final String[] EATS_PACKAGE_NAMES = - {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats.internal", "com.ubercab.eats.nightly", "com.ubercab.eats"}; + {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats.nightly", "com.ubercab.eats"}; public static final String PLATFORM = "android"; // Both com.ubercab.eats and com.ubercab.eats.nightly share the same hash From 2107517bed9977627576c508e223123a8f3f7095 Mon Sep 17 00:00:00 2001 From: Marvyn Leroy Date: Thu, 7 Mar 2019 18:39:48 -0500 Subject: [PATCH 113/165] Revert previous code --- .../java/com/uber/sdk/android/core/utils/AppProtocol.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 77d35f92..75d9ceb5 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -38,11 +38,8 @@ public class AppProtocol { {"com.ubercab.eats.debug", "com.ubercab.eats.exo", "com.ubercab.eats.nightly", "com.ubercab.eats"}; public static final String PLATFORM = "android"; - // Both com.ubercab.eats and com.ubercab.eats.nightly share the same hash - private static final String UBER_EATS_HASH = "ae0b86995f174533b423067837beba13d922fbb0"; - // For com.ubercab.eats.internal - private static final String UBER_EATS_INTERNAL_HASH = "9a715f0cbb44b01e3c41c9bda30feba107561e79"; private static final String UBER_RIDER_HASH = "411c40b31f6d01dac68d711df99b6eafeec8e73b"; + private static final String UBER_EATS_HASH = "ae0b86995f174533b423067837beba13d922fbb0"; private static final String HASH_ALGORITHM_SHA1 = "SHA-1"; private static final int DEFAULT_MIN_VERSION = 0; @@ -53,7 +50,6 @@ private static HashSet buildAppSignatureHashes() { HashSet set = new HashSet<>(); set.add(UBER_RIDER_HASH); set.add(UBER_EATS_HASH); - set.add(UBER_EATS_INTERNAL_HASH); return set; } From 06e43f6cae59b687452bb6c03ec16e3c6a4ce2ca Mon Sep 17 00:00:00 2001 From: Will Vanderhoef Date: Thu, 30 May 2019 17:09:33 -0400 Subject: [PATCH 114/165] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 341e4ea2..79f2cb31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ v0.10.2 - TBD v0.10.1 - 02/27/2019 ------------- +### Fixed + - [Issue #153](https://github.com/uber/rides-android-sdk/issues/153) NullPointerException when login via SSO without setting product flow priority + - [Issue #151](https://github.com/uber/rides-android-sdk/issues/151) Login throws IllegalStateException when using only CustomScopes + + v0.10.0 - 12/14/2018 ------------ From c4cac9022df1071f26943f479d8f9c94be152a61 Mon Sep 17 00:00:00 2001 From: Adam Rogal Date: Tue, 29 Oct 2019 14:36:49 -0700 Subject: [PATCH 115/165] fix proposal --- .gitignore | 1 + .../sdk/android/core/auth/LoginActivity.java | 6 ++++++ .../android/core/utils/CustomTabsHelper.java | 19 +++++++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d12963ef..ed5c9cf3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ obj .DS_Store log.txt +.vscode diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 2bdc45ff..24ef9747 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -199,6 +199,12 @@ protected void init() { } } + @Override + protected void onDestroy() { + super.onDestroy(); + customTabsHelper.destory(this); + } + protected void loadUrl() { Intent intent = getIntent(); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java index 471ba75c..6e808434 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java @@ -51,6 +51,8 @@ public class CustomTabsHelper { private static String packageNameToUse; + private CustomTabsServiceConnection connection; + public CustomTabsHelper() {} /** @@ -69,7 +71,7 @@ public void openCustomTab( final String packageName = getPackageNameToUse(context); if (packageName != null) { - final CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { + connection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) { client.warmup(0L); // This prevents backgrounding after redirection @@ -78,18 +80,27 @@ public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabs customTabsIntent.intent.setData(uri); customTabsIntent.launchUrl(context, uri); } + @Override - public void onServiceDisconnected(ComponentName name) {} + public void onServiceDisconnected(ComponentName name) { + } }; CustomTabsClient.bindCustomTabsService(context, packageName, connection); } else if (fallback != null) { fallback.openUri(context, uri); } else { - Log.e(UberSdk.UBER_SDK_LOG_TAG, - "Use of openCustomTab without Customtab support or a fallback set"); + Log.e(UberSdk.UBER_SDK_LOG_TAG, "Use of openCustomTab without Customtab support or a fallback set"); } } + /** + * Called to clean up the CustomTab when the parentActivity is destroyed. + */ + public void onDestroy(Activity parentActivity) { + parentActivity.unbindService(connection); + connection = null; + } + /** * Goes through all apps that handle VIEW intents and have a warmup service. Picks * the one chosen by the user if there is one, otherwise makes a best effort to return a From 7a70a1ad6aab725aa90d2f196198fa141dc8d1de Mon Sep 17 00:00:00 2001 From: Adam Rogal Date: Tue, 29 Oct 2019 15:04:24 -0700 Subject: [PATCH 116/165] fix syntax --- .../main/java/com/uber/sdk/android/core/auth/LoginActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 24ef9747..21dd2bda 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -202,7 +202,7 @@ protected void init() { @Override protected void onDestroy() { super.onDestroy(); - customTabsHelper.destory(this); + customTabsHelper.onDestroy(this); } protected void loadUrl() { From ee405a47f69126e286d73d2d2d88200feaab386b Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 3 Dec 2019 14:16:08 -0800 Subject: [PATCH 117/165] [Gradle Release Plugin] - pre tag commit: 'v0.10.2'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f2cb31..c23411d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.10.2 - TBD +v0.10.2 - 12/03/2019 ------------- v0.10.1 - 02/27/2019 diff --git a/gradle.properties b/gradle.properties index 462c5907..992d35bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Wed, 27 Feb 2019 13:55:32 -0500 +#Tue, 03 Dec 2019 14:12:31 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.2-SNAPSHOT -VERSION_NAME=0.10.2-SNAPSHOT +version=0.10.2 +VERSION_NAME=0.10.2 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From c55bec96dfd129b3b64ca833aecfd74cd2af1c81 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Tue, 3 Dec 2019 14:16:24 -0800 Subject: [PATCH 118/165] [Gradle Release Plugin] - new version commit: 'v0.10.3-SNAPSHOT'. --- CHANGELOG.md | 3 +++ gradle.properties | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c23411d5..ee246e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v0.10.3 - TBD +------------- + v0.10.2 - 12/03/2019 ------------- diff --git a/gradle.properties b/gradle.properties index 992d35bf..f0518966 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Tue, 03 Dec 2019 14:12:31 -0800 +#Tue, 03 Dec 2019 14:16:24 -0800 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.2 -VERSION_NAME=0.10.2 +version=0.10.3-SNAPSHOT +VERSION_NAME=0.10.3-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 405f53a8b7c6dd795f9d691f874c4c1fce093380 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Thu, 30 Apr 2020 18:23:59 +0530 Subject: [PATCH 119/165] fix crash in CustomTabsHelper if connection is null --- .../com/uber/sdk/android/core/utils/CustomTabsHelper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java index 6e808434..614f1587 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java @@ -97,8 +97,10 @@ public void onServiceDisconnected(ComponentName name) { * Called to clean up the CustomTab when the parentActivity is destroyed. */ public void onDestroy(Activity parentActivity) { - parentActivity.unbindService(connection); - connection = null; + if (connection != null) { + parentActivity.unbindService(connection); + connection = null; + } } /** From 4fb5a36443a0c725e5e5a7139072072497aa1b65 Mon Sep 17 00:00:00 2001 From: Rafael Toledo Date: Thu, 6 May 2021 15:07:43 -0300 Subject: [PATCH 120/165] Remove Travis and migrate to Github Actions --- .github/workflows/build.yml | 81 +++++++++++++++++++++++++++++++++++++ .travis.yml | 51 ----------------------- README.md | 2 +- 3 files changed, 82 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..2ac29e28 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,81 @@ +name: CI + +on: [push, pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.3 + - name: Install JDK + uses: actions/setup-java@v2.0.0 + with: + distribution: 'adopt' + java-version: '8' + - name: Lint and Unit tests + run: ./gradlew check --stacktrace + - name: Upload lint and test reports + if: always() + uses: actions/upload-artifact@v2.2.3 + with: + name: execution-reports + path: | + ./core-android/build/reports + ./rides-android/build/reports + + test: + runs-on: macOS-latest # enables hardware acceleration in the virtual machine, required for emulator testing + strategy: + matrix: + api-level: [ 21, 23, 26, 29, 30 ] + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.3 + - name: Install JDK + uses: actions/setup-java@v2.0.0 + with: + distribution: 'adopt' + java-version: '8' + - name: Emulator tests + uses: reactivecircus/android-emulator-runner@v2.15.0 + with: + api-level: ${{ matrix.api-level }} + target: google_apis + arch: x86 + disable-animations: true + script: ./gradlew connectedCheck --stacktrace + - name: Upload instrumented test reports + if: always() + uses: actions/upload-artifact@v2.2.3 + with: + name: test-reports + path: | + ./core-android/build/reports + ./rides-android/build/reports + + upload-snapshots: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' + needs: + - check + - test + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.3 + - name: Install JDK + uses: actions/setup-java@v2.0.0 + with: + distribution: 'adopt' + java-version: '8' + - name: Upload snapshots + run: ./gradlew uploadArchives --stacktrace + env: + SONATYPE_NEXUS_USERNAME: ${{ secrets.SonatypeUsername }} + SONATYPE_NEXUS_PASSWORD: ${{ secrets.SonatypePassword }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index aca24380..00000000 --- a/.travis.yml +++ /dev/null @@ -1,51 +0,0 @@ -language: android - -android: - components: - # Update tools and then platform-tools explicitly so lint gets an updated database. Can be removed once 3.0 is out. - - tools - - platform-tools - -jdk: - - oraclejdk8 - -before_install: - # Install SDK license so Android Gradle plugin can install deps. - - mkdir "$ANDROID_HOME/licenses" || true - - echo "$LICENSES_HASH" > "$ANDROID_HOME/licenses/android-sdk-license" - - echo "$LICENSES_HASH_TWO" >> "$ANDROID_HOME/licenses/android-sdk-license" - # Install the rest of tools (e.g., avdmanager) - - yes | sdkmanager tools - # Install the system image - - sdkmanager "system-images;android-18;default;armeabi-v7a" - # Create and start emulator for the script. Meant to race the install task. - - echo no | avdmanager create avd --force -n test -k "system-images;android-18;default;armeabi-v7a" - - $ANDROID_HOME/emulator/emulator -avd test -no-window > /dev/null 2>&1 & - -install: ./gradlew clean assemble assembleAndroidTest --stacktrace - -before_script: - - android-wait-for-emulator - - adb shell input keyevent 82 - -script: - - ./gradlew check connectedCheck --stacktrace - -after_success: - - .buildscript/deploy_snapshot.sh - -env: - global: - - secure: "g5g+VmfqVF7PMuTl00NUQpX4pFohsEVb/SUK3adFWdT5SKOZgi28ArwUEmCMbG/he5UyyQJkv38CKfa+6jZM1cDDNkICGEjM3YCImJyCDpUKLKLFlsvDtAPK7H8rUpLgmGOHQONObtJbhtkmWg+0fr6TRj+yltZZl/6dKZ7Im4aeeMDE03Hy8MubvxRqANF3lT1bJMLUTIAP/gHSD46AKhlbnUJHW5QXqKJqMbtt3nyZNZ3aWPVMoGc+zTbpMWD6dvCH0Etc/nEatMINLunHEUf59CVEiCmQSHrWHsyDn75OiVCJUEzj9euTKE1Kz5OjKrPNYPlW1V840EOLdr/I3rz50gFmMjSyNZcd6D4W6jygOC0QDm57ISHO/Jtl4iLaPzqKMRT4daiyqWZJrnFB8Xlt8CjCxxxXT0A1ZXgro1Auoaa4OVg53Ey40CuRABHbVu6KnskcT5A52XHlDxh3wi5LnPaLH5LzB5/erFzzgLn0GPYTVKInKen6/cY+/+h84rMgS8VkKegl3oUuz1Kzej1GkxnFzdwm3j+1CuDTkadmlpn92+N6wXlG628cs7e93m0QhxXsd+mIaAwyicQeelsOeaAXTp72xfvXE7RQA2YNXjAqVoXu0kTWlXs2aDlm3HHOS4FR0kR96TJarqpinD+zAzEseaP4rMCgqdOsKok=" - - secure: "cp+/y+Zw6ijlK/JBigffDVhQyVhZTfSfgkkM5V0USlJIS2c3N7e1vruiXIcRLEIfzgTbU0veVn37NDIDMkoUDlUY9q2p0uM+xwoO8URYK/SqOhevFSd+QgMX9QwnaV5reD6aZzrz/SI6AbA6hAmIRIXZGkxRfgsB1nJN4m1Fwiwib2IXPHmBa+8d1vS7yU36FrY/3fHthgz1T1M+VDVFmbIGbBqCj5GR6CF+TcDcEQW3UvH671s7IAzuY8VzopeQjaqHE+U+8wrkg0mmiX6/B3a9nH6G/rfaeLTpomb0QCojk+W8en85f7KdK6/7SEiKU0NJmJ62ccc8n0h1VxhWYx/r2S2nVM8FfVuOTOPWuGZU0ULI1ylqAYgppBMp49eOlAJV5QzyBB7Q+py20wEPBisIlvZXLytNm7RWojiTt1wm2PaEhFzYmfPgLlViFDv7N+hLhxproDpw+c1XhhM2ZPyxW8oUriRxyMRqXkJClEgNg+0X9IyFzGc93S7yw0u5SMwhC735vjk3G4sSL/bZjre5zSc/XXM+HqIzWP+KUwRZqdcwW7uuGMB8LUWAHiCrZUuCRL25cisKW/xArH3Sg4nrFG3VP+8yMgRRVJWMtCandSzsJcBmiFPEqsb12eTo2hQfJzoKMX9HcOitfkjn1U1zsJm1ycsG61prF7fhdHg=" - -branches: - except: - - gh-pages - -notifications: - email: false - -cache: - directories: - - $HOME/.gradle diff --git a/README.md b/README.md index 6209f33a..e4eb2d81 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Uber Rides Android SDK (beta) [![Build Status](https://travis-ci.org/uber/rides-android-sdk.svg?branch=master)](https://travis-ci.org/uber/rides-android-sdk) +# Uber Rides Android SDK (beta) ![Build Status](https://github.com/uber/rides-android-sdk/workflows/CI/badge.svg) Official Android SDK to support: - Ride Request Button From 34666dfacf4029b00f4f9bae91507f2a17e73889 Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Wed, 30 Jun 2021 09:49:47 -0700 Subject: [PATCH 121/165] Migrate to AndroidX --- core-android/build.gradle | 2 +- .../java/com/uber/sdk/android/core/UberButton.java | 14 +++++++------- .../java/com/uber/sdk/android/core/UberSdk.java | 2 +- .../java/com/uber/sdk/android/core/UberStyle.java | 10 +++++----- .../sdk/android/core/auth/AccessTokenManager.java | 6 +++--- .../com/uber/sdk/android/core/auth/AuthUtils.java | 8 ++------ .../sdk/android/core/auth/AuthenticationError.java | 2 +- .../core/auth/LegacyUriRedirectHandler.java | 4 ++-- .../uber/sdk/android/core/auth/LoginActivity.java | 8 +++----- .../uber/sdk/android/core/auth/LoginButton.java | 10 +++++----- .../uber/sdk/android/core/auth/LoginCallback.java | 2 +- .../uber/sdk/android/core/auth/LoginManager.java | 6 +++--- .../uber/sdk/android/core/auth/SsoDeeplink.java | 4 ++-- .../sdk/android/core/install/SignupDeeplink.java | 2 +- .../uber/sdk/android/core/utils/AppProtocol.java | 6 +++--- .../sdk/android/core/utils/CustomTabsHelper.java | 10 +++++----- .../sdk/android/core/utils/PackageManagers.java | 4 ++-- .../uber/sdk/android/core/utils/Preconditions.java | 2 +- .../com/uber/sdk/android/core/utils/Utility.java | 2 +- .../android/core/auth/AccessTokenPreferences.java | 4 ++-- .../sdk/android/core/auth/LoginActivityTest.java | 2 +- .../sdk/android/core/auth/LoginButtonTest.java | 2 +- .../sdk/android/core/auth/LoginManagerTest.java | 4 ++-- gradle.properties | 2 ++ gradle/dependencies.gradle | 14 +++++++------- rides-android/build.gradle | 2 +- .../uber/sdk/android/rides/RequestDeeplink.java | 2 +- .../sdk/android/rides/RequestDeeplinkBehavior.java | 2 +- .../com/uber/sdk/android/rides/RideParameters.java | 4 ++-- .../sdk/android/rides/RideRequestActivity.java | 12 ++++++------ .../android/rides/RideRequestActivityBehavior.java | 2 +- .../uber/sdk/android/rides/RideRequestButton.java | 6 +++--- .../sdk/android/rides/RideRequestDeeplink.java | 8 ++++---- .../uber/sdk/android/rides/RideRequestView.java | 6 +++--- .../internal/RideRequestButtonController.java | 6 +++--- .../rides/internal/RideRequestButtonView.java | 2 +- .../sdk/android/rides/internal/TimeDelegate.java | 2 +- .../android/rides/internal/TimePriceDelegate.java | 2 +- .../sdk/android/rides/RideRequestDeeplinkTest.java | 2 +- .../java/com/uber/sdk/android/rides/TestUtils.java | 2 +- .../sdk/android/samples/LoginSampleActivity.java | 4 ++-- .../sdk/android/rides/samples/SampleActivity.java | 8 +------- 42 files changed, 97 insertions(+), 107 deletions(-) diff --git a/core-android/build.gradle b/core-android/build.gradle index 8f4979c9..f006e1ae 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -37,7 +37,7 @@ android { targetSdkVersion deps.build.targetSdkVersion versionName VERSION_NAME consumerProguardFiles 'consumer-proguard-rules.txt' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } compileOptions { diff --git a/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java b/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java index 9626bb0d..6fd48f8f 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/UberButton.java @@ -29,13 +29,13 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Typeface; -import android.support.annotation.DrawableRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.annotation.StyleRes; -import android.support.annotation.VisibleForTesting; -import android.support.v7.widget.AppCompatButton; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.widget.AppCompatButton; import android.util.AttributeSet; import android.view.Gravity; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java b/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java index 907319dc..64bca388 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/UberSdk.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.core; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.core.client.SessionConfiguration; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/UberStyle.java b/core-android/src/main/java/com/uber/sdk/android/core/UberStyle.java index b49ead1e..39a530a6 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/UberStyle.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/UberStyle.java @@ -24,11 +24,11 @@ import android.content.Context; import android.content.res.TypedArray; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StyleRes; -import android.support.annotation.StyleableRes; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; +import androidx.annotation.StyleableRes; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; public enum UberStyle { diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AccessTokenManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AccessTokenManager.java index 3b4c16c8..e1c77e77 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AccessTokenManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AccessTokenManager.java @@ -24,9 +24,9 @@ import android.content.Context; import android.content.SharedPreferences; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import android.webkit.CookieManager; import com.uber.sdk.core.auth.AccessToken; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index 2f658878..9f96f20b 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -25,11 +25,9 @@ import android.app.Activity; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ResolveInfo; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Base64; import android.webkit.WebView; @@ -38,10 +36,8 @@ import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Locale; import java.util.Set; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java index e7a68a31..3f1ee11d 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthenticationError.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.core.auth; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.Locale; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java index 8ec61f17..d7134fc2 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandler.java @@ -3,8 +3,8 @@ import android.app.Activity; import android.content.Context; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.v4.util.Pair; +import androidx.annotation.NonNull; +import androidx.core.util.Pair; import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.utils.Utility; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 21dd2bda..6ccdfa1f 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -29,9 +29,9 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.support.customtabs.CustomTabsIntent; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.browser.customtabs.CustomTabsIntent; import android.text.TextUtils; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; @@ -47,8 +47,6 @@ import com.uber.sdk.core.client.SessionConfiguration; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; /** * {@link android.app.Activity} that shows web view for Uber user authentication and authorization. diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java index ee38ad6c..d4aa6071 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginButton.java @@ -26,11 +26,11 @@ import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.annotation.StyleRes; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java index a8bc569b..e4418723 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginCallback.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.core.auth; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.core.auth.AccessToken; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index e495928b..ca26daab 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -24,9 +24,9 @@ import android.app.Activity; import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import android.util.Log; import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.UberSdk; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java index 57748fb8..589244de 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/SsoDeeplink.java @@ -28,8 +28,8 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import android.util.Log; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.Deeplink; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/install/SignupDeeplink.java b/core-android/src/main/java/com/uber/sdk/android/core/install/SignupDeeplink.java index fee1ea3a..bd8a8491 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/install/SignupDeeplink.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/install/SignupDeeplink.java @@ -25,7 +25,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.R; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java index 75d9ceb5..954b5bd2 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/AppProtocol.java @@ -6,9 +6,9 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import android.util.Base64; import android.util.Pair; import com.uber.sdk.android.core.SupportedAppType; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java index 614f1587..b94e8a42 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/CustomTabsHelper.java @@ -22,11 +22,11 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.customtabs.CustomTabsClient; -import android.support.customtabs.CustomTabsIntent; -import android.support.customtabs.CustomTabsServiceConnection; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.browser.customtabs.CustomTabsClient; +import androidx.browser.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsServiceConnection; import android.text.TextUtils; import android.util.Log; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/PackageManagers.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/PackageManagers.java index 5b17bb90..09957c16 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/PackageManagers.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/PackageManagers.java @@ -25,8 +25,8 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; public class PackageManagers { diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java index 10a4cf73..4e3d1f4f 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Preconditions.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.core.utils; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.Collection; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java index 6ccade0f..5a4b08d7 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/utils/Utility.java @@ -5,7 +5,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.Log; import com.uber.sdk.android.core.R; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenPreferences.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenPreferences.java index 69bf51b0..6cb503a3 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenPreferences.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AccessTokenPreferences.java @@ -24,8 +24,8 @@ import android.content.Context; import android.content.SharedPreferences; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.uber.sdk.core.auth.AccessToken; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index 888f7d92..aa2df67a 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.net.Uri; -import android.support.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsIntent; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.uber.sdk.android.core.RobolectricTestBase; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java index 7fdc1c71..7bc84617 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginButtonTest.java @@ -25,7 +25,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.AttributeSet; import com.google.common.collect.Sets; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index fc2aab3d..66063414 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.common.collect.ImmutableList; import com.uber.sdk.android.core.RobolectricTestBase; import com.uber.sdk.android.core.SupportedAppType; @@ -36,7 +36,7 @@ import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; -import edu.emory.mathcs.backport.java.util.Collections; + import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; diff --git a/gradle.properties b/gradle.properties index f0518966..751eb561 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,3 +21,5 @@ GITHUB_OWNER=uber GITHUB_REPO=rides-android-sdk GITHUB_DOWNLOAD_PREFIX=https\://github.com/uber/rides-android-sdk/releases/download/ GITHUB_BRANCH=master +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index c40d944d..add28e0d 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -22,18 +22,18 @@ def versions = [ def build = [ gradleVersion: '4.9', - buildToolsVersion: '27.0.3', - compileSdkVersion: 27, + buildToolsVersion: '28.0.2', + compileSdkVersion: 28, ci: 'true' == System.getenv('CI'), minSdkVersion: 15, - targetSdkVersion: 27, + targetSdkVersion: 28, repositories: [ plugins: 'https://plugins.gradle.org/m2/' ], gradlePlugins: [ - android: 'com.android.tools.build:gradle:3.1.0', + android: 'com.android.tools.build:gradle:3.2.0', release: 'net.researchgate:gradle-release:2.1.2', github: 'co.riiid:gradle-github-plugin:0.4.2', cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1', @@ -45,9 +45,9 @@ def misc = [ ] def support = [ - annotations: "com.android.support:support-annotations:${versions.support}", - appCompat: "com.android.support:appcompat-v7:${versions.support}", - chrometabs: "com.android.support:customtabs:${versions.support}", + annotations: 'androidx.annotation:annotation:1.0.0', + appCompat : 'androidx.appcompat:appcompat:1.0.0', + chrometabs : 'androidx.browser:browser:1.0.0', ] def test = [ diff --git a/rides-android/build.gradle b/rides-android/build.gradle index 5ede030b..17fa454f 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -37,7 +37,7 @@ android { targetSdkVersion deps.build.targetSdkVersion versionName VERSION_NAME consumerProguardFiles 'consumer-proguard-rules.txt' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } compileOptions { diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java index 03da7bd1..fc6e512e 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java @@ -24,7 +24,7 @@ import android.content.Context; import android.net.Uri; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.android.core.utils.CustomTabsHelper; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java index 72a1baa4..e1a5bfa4 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RequestDeeplinkBehavior.java @@ -23,7 +23,7 @@ package com.uber.sdk.android.rides; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.core.client.SessionConfiguration; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java index cbb0d566..aee56870 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideParameters.java @@ -24,8 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * Represents the parameters for an Uber ride. diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index 8964c311..19628edc 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -30,12 +30,12 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.annotation.VisibleForTesting; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import com.uber.sdk.android.core.auth.AccessTokenManager; import com.uber.sdk.android.core.auth.AuthenticationError; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java index 471b18b2..167f9dc7 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivityBehavior.java @@ -25,7 +25,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.auth.AccessTokenManager; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java index c7e554ab..8f28562f 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestButton.java @@ -28,9 +28,9 @@ 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.support.annotation.StyleRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java index 393111c4..99846e11 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestDeeplink.java @@ -24,10 +24,10 @@ import android.content.Context; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.customtabs.CustomTabsIntent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.browser.customtabs.CustomTabsIntent; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.utils.AppProtocol; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java index de742995..053e15b0 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestView.java @@ -27,9 +27,9 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import android.util.AttributeSet; import android.webkit.GeolocationPermissions; import android.webkit.WebChromeClient; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java index 9188d83c..4071b964 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonController.java @@ -22,9 +22,9 @@ package com.uber.sdk.android.rides.internal; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.uber.sdk.android.rides.RideParameters; import com.uber.sdk.android.rides.RideRequestButtonCallback; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonView.java b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonView.java index 7be4e7e0..7de310c8 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonView.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/RideRequestButtonView.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.rides.internal; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.rides.client.model.PriceEstimate; import com.uber.sdk.rides.client.model.TimeEstimate; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimeDelegate.java b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimeDelegate.java index c28a6ef0..81493c22 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimeDelegate.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimeDelegate.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.rides.internal; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.android.rides.RideRequestButtonCallback; import com.uber.sdk.rides.client.error.ApiError; diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimePriceDelegate.java b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimePriceDelegate.java index dcf10c80..a6a99d9f 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimePriceDelegate.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/internal/TimePriceDelegate.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.rides.internal; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.uber.sdk.android.rides.RideRequestButtonCallback; import com.uber.sdk.rides.client.model.PriceEstimate; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java index 358dc2b4..85373545 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestDeeplinkTest.java @@ -24,7 +24,7 @@ import android.content.Context; import android.net.Uri; -import android.support.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsIntent; import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.utils.AppProtocol; diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java b/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java index c052fb5d..b73e1fb3 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/TestUtils.java @@ -22,7 +22,7 @@ package com.uber.sdk.android.rides; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.TextUtils; import com.google.common.io.Files; diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 1123de90..9df846c5 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -27,8 +27,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; 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 index bfaebc9a..0be41b4e 100644 --- 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 @@ -22,13 +22,11 @@ package com.uber.sdk.android.rides.samples; -import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.content.Intent; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -36,13 +34,9 @@ import com.uber.sdk.android.core.Deeplink; import com.uber.sdk.android.core.auth.AccessTokenManager; -import com.uber.sdk.android.core.auth.AuthenticationError; import com.uber.sdk.android.rides.RideParameters; -import com.uber.sdk.android.rides.RideRequestActivity; -import com.uber.sdk.android.rides.RideRequestActivityBehavior; import com.uber.sdk.android.rides.RideRequestButton; import com.uber.sdk.android.rides.RideRequestButtonCallback; -import com.uber.sdk.android.rides.RideRequestViewError; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.client.ServerTokenSession; From f94d75eecd8b1f2da2f35d4df76c6b83a41a5813 Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Wed, 30 Jun 2021 09:57:29 -0700 Subject: [PATCH 122/165] version configuration change --- gradle/dependencies.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index add28e0d..8b68ee75 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -16,7 +16,7 @@ def versions = [ androidTest: '0.5', - support: '27.1.1', + androidxVersion: '1.0.0', uberJava: '0.8.0', ] @@ -45,9 +45,9 @@ def misc = [ ] def support = [ - annotations: 'androidx.annotation:annotation:1.0.0', - appCompat : 'androidx.appcompat:appcompat:1.0.0', - chrometabs : 'androidx.browser:browser:1.0.0', + annotations: "androidx.annotation:annotation:${versions.androidxVersion}", + appCompat : "androidx.appcompat:appcompat:${versions.androidxVersion}", + chrometabs : "androidx.browser:browser:${versions.androidxVersion}", ] def test = [ From 870bea405f0c1a39be8b70e4d7394d391b53eb2a Mon Sep 17 00:00:00 2001 From: Rafael Toledo Date: Mon, 5 Jul 2021 19:18:55 -0300 Subject: [PATCH 123/165] Change how the auth data is published into the config yaml --- .github/workflows/build.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ac29e28..8b69c107 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.0.3 + uses: gradle/wrapper-validation-action@v1 - name: Install JDK - uses: actions/setup-java@v2.0.0 + uses: actions/setup-java@v2 with: distribution: 'adopt' java-version: '8' @@ -19,7 +19,7 @@ jobs: run: ./gradlew check --stacktrace - name: Upload lint and test reports if: always() - uses: actions/upload-artifact@v2.2.3 + uses: actions/upload-artifact@v2 with: name: execution-reports path: | @@ -33,16 +33,16 @@ jobs: api-level: [ 21, 23, 26, 29, 30 ] steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.0.3 + uses: gradle/wrapper-validation-action@v1 - name: Install JDK - uses: actions/setup-java@v2.0.0 + uses: actions/setup-java@v2 with: distribution: 'adopt' java-version: '8' - name: Emulator tests - uses: reactivecircus/android-emulator-runner@v2.15.0 + uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} target: google_apis @@ -51,7 +51,7 @@ jobs: script: ./gradlew connectedCheck --stacktrace - name: Upload instrumented test reports if: always() - uses: actions/upload-artifact@v2.2.3 + uses: actions/upload-artifact@v2 with: name: test-reports path: | @@ -60,22 +60,22 @@ jobs: upload-snapshots: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' needs: - check - test steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2 - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.0.3 + uses: gradle/wrapper-validation-action@v1 - name: Install JDK - uses: actions/setup-java@v2.0.0 + uses: actions/setup-java@v2 with: distribution: 'adopt' java-version: '8' - name: Upload snapshots run: ./gradlew uploadArchives --stacktrace env: - SONATYPE_NEXUS_USERNAME: ${{ secrets.SonatypeUsername }} - SONATYPE_NEXUS_PASSWORD: ${{ secrets.SonatypePassword }} + ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SonatypeUsername }} + ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SonatypePassword }} From 4a1fa72dc3e1c5205695524c7e775b5c7f01d51e Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Wed, 11 Aug 2021 18:29:55 -0700 Subject: [PATCH 124/165] init --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b69c107..55f7f286 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: runs-on: macOS-latest # enables hardware acceleration in the virtual machine, required for emulator testing strategy: matrix: - api-level: [ 21, 23, 26, 29, 30 ] + api-level: [ 29, 30 ] steps: - name: Checkout uses: actions/checkout@v2 From 9cad23395f01e876d6c327fc1f16191dd79bf5af Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Thu, 12 Aug 2021 14:18:59 -0700 Subject: [PATCH 125/165] Upgrade AndroidX RoboElectric --- .../sdk/android/core/RobolectricTestBase.java | 2 +- .../auth/LegacyUriRedirectHandlerTest.java | 2 +- .../android/core/auth/LoginActivityTest.java | 27 +++++++++---------- .../core/auth/OAuthWebViewClientTest.java | 4 +-- .../android/core/auth/SsoDeeplinkTest.java | 7 ++--- gradle/dependencies.gradle | 4 +-- .../rides/RideRequestActivityTest.java | 9 ++++--- .../android/rides/RobolectricTestBase.java | 2 +- 8 files changed, 29 insertions(+), 28 deletions(-) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/RobolectricTestBase.java b/core-android/src/test/java/com/uber/sdk/android/core/RobolectricTestBase.java index be796f8c..99a4e1c4 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/RobolectricTestBase.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/RobolectricTestBase.java @@ -30,7 +30,7 @@ import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 21) +@Config(sdk = 21) public abstract class RobolectricTestBase { @Rule diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java index 38a77e12..63fea4aa 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LegacyUriRedirectHandlerTest.java @@ -216,7 +216,7 @@ private void assertLastLog(String message) { private void assertNoLogs() { List logItemList = ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG); - assertThat(ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG)).isNull(); + assertThat(ShadowLog.getLogsForTag(UberSdk.UBER_SDK_LOG_TAG)).isEmpty(); } private void assertDialogShown() { diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index aa2df67a..295f87cf 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -41,9 +41,9 @@ import org.mockito.Mock; import org.robolectric.Robolectric; import org.robolectric.Shadows; +import org.robolectric.android.controller.ActivityController; import org.robolectric.shadows.ShadowActivity; import org.robolectric.shadows.ShadowWebView; -import org.robolectric.util.ActivityController; import java.util.ArrayList; import java.util.Set; @@ -91,7 +91,7 @@ public void setup() { .build(); Intent data = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN); - loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(data).get(); + loginActivity = Robolectric.buildActivity(LoginActivity.class, data).get(); when(ssoDeeplinkFactory.getSsoDeeplink(any(LoginActivity.class), eq(productPriority), any(SessionConfiguration.class))).thenReturn(ssoDeeplink); @@ -116,8 +116,7 @@ public void onLoginLoad_withEmptyScopes_shouldReturnErrorResultIntent() { Intent intent = new Intent(); intent.putExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION, new SessionConfiguration.Builder().setClientId(CLIENT_ID).build()); - ActivityController controller = Robolectric.buildActivity(LoginActivity.class) - .withIntent(intent) + ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent) .create(); ShadowActivity shadowActivity = shadowOf(controller.get()); @@ -136,7 +135,7 @@ public void onLoginLoad_withNullResponseType_shouldReturnErrorResultIntent() { intent.putExtra(LoginActivity.EXTRA_RESPONSE_TYPE, (ResponseType) null); ActivityController controller = Robolectric.buildActivity(LoginActivity.class) - .withIntent(intent) + .newIntent(intent) .create(); ShadowActivity shadowActivity = shadowOf(controller.get()); @@ -153,7 +152,7 @@ public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, loginConfiguration, ResponseType.TOKEN, false, true, true); - ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); loginActivity.ssoDeeplinkFactory = ssoDeeplinkFactory; @@ -169,7 +168,7 @@ public void onLoginLoad_withSsoEnabled_andNotSupported_shouldReturnErrorResultIn Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, loginConfiguration, ResponseType.TOKEN, false, true, true); - ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).newIntent(intent); loginActivity = controller.get(); loginActivity.ssoDeeplinkFactory = ssoDeeplinkFactory; ShadowActivity shadowActivity = shadowOf(loginActivity); @@ -189,7 +188,7 @@ public void onLoginLoad_withResponseTypeCode_andForceWebview_shouldLoadWebview() Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.CODE, true); - loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get(); + loginActivity = Robolectric.buildActivity(LoginActivity.class, intent).create().get(); ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration); @@ -201,7 +200,7 @@ public void onLoginLoad_withResponseTypeCode_andNotForceWebview_shouldLoadChrome Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.CODE, false); - ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); loginActivity.customTabsHelper = customTabsHelper; controller.create(); @@ -216,7 +215,7 @@ public void onLoginLoad_withResponseTypeToken_andForceWebview_andGeneralScopes_s Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN, true); - loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get(); + loginActivity = Robolectric.buildActivity(LoginActivity.class, intent).create().get(); ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); @@ -228,7 +227,7 @@ public void onLoginLoad_withResponseTypeToken_andNotForceWebview_andGeneralScope Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN, false); - ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + ActivityController controller = Robolectric.buildActivity(LoginActivity.class).newIntent(intent); loginActivity = controller.get(); loginActivity.customTabsHelper = customTabsHelper; controller.create(); @@ -248,7 +247,7 @@ public void onLoginLoad_withResponseTypeToken_andForceWebview_andPrivilegedScope Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN, true); - loginActivity = Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get(); + loginActivity = Robolectric.buildActivity(LoginActivity.class, intent).create().get(); ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); @@ -265,7 +264,7 @@ public void onLoginLoad_withResponseTypeToken_andNotForceWebview_andPrivilegedSc Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.TOKEN, false); - ActivityController controller = Robolectric.buildActivity(LoginActivity.class).withIntent(intent); + ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); loginActivity.customTabsHelper = customTabsHelper; controller.create(); @@ -285,7 +284,7 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), new ArrayList(), loginConfiguration, ResponseType.TOKEN, true, false, true); - ShadowActivity shadowActivity = shadowOf(Robolectric.buildActivity(LoginActivity.class).withIntent(intent).create().get()); + ShadowActivity shadowActivity = shadowOf(Robolectric.buildActivity(LoginActivity.class, intent).create().get()); final Intent signupDeeplinkIntent = shadowActivity.peekNextStartedActivity(); assertThat(signupDeeplinkIntent.getData().toString()).isEqualTo(SIGNUP_DEEPLINK_URL); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java index a04736c4..9ebae966 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/OAuthWebViewClientTest.java @@ -36,8 +36,8 @@ import org.mockito.ArgumentCaptor; import org.robolectric.Robolectric; import org.robolectric.Shadows; +import org.robolectric.android.controller.ActivityController; import org.robolectric.shadows.ShadowActivity; -import org.robolectric.util.ActivityController; import java.util.Arrays; import java.util.Collection; @@ -73,7 +73,7 @@ public void onLoadLoginView_withNoRedirectUrl_shouldReturnError() { Intent intent = new Intent(); intent.putExtra(LoginActivity.EXTRA_SESSION_CONFIGURATION, config); final ActivityController controller = Robolectric.buildActivity(LoginActivity.class) - .withIntent(intent); + .newIntent(intent); final ShadowActivity shadowActivity = Shadows.shadowOf(controller.get()); controller.create(); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java index 4ad5a378..8277a991 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/SsoDeeplinkTest.java @@ -41,7 +41,7 @@ import org.mockito.Mock; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; -import org.robolectric.res.builder.RobolectricPackageManager; +import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowResolveInfo; import java.util.Arrays; @@ -65,6 +65,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; public class SsoDeeplinkTest extends RobolectricTestBase { @@ -82,7 +83,7 @@ public class SsoDeeplinkTest extends RobolectricTestBase { Activity activity; - RobolectricPackageManager packageManager; + protected ShadowPackageManager packageManager; ResolveInfo resolveInfo; @@ -97,7 +98,7 @@ public void setUp() { redirectIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(REDIRECT_URI)); redirectIntent.setPackage(activity.getPackageName()); resolveInfo = ShadowResolveInfo.newResolveInfo("", activity.getPackageName()); - packageManager = RuntimeEnvironment.getRobolectricPackageManager(); + packageManager = shadowOf(RuntimeEnvironment.application.getPackageManager()); packageManager.addResolveInfoForIntent(redirectIntent, resolveInfo); ssoDeeplink = new SsoDeeplink.Builder(activity) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 8b68ee75..09182ae4 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -47,14 +47,14 @@ def misc = [ def support = [ annotations: "androidx.annotation:annotation:${versions.androidxVersion}", appCompat : "androidx.appcompat:appcompat:${versions.androidxVersion}", - chrometabs : "androidx.browser:browser:${versions.androidxVersion}", + chrometabs : "androidx.browser:browser:${versions.androidxVersion}" ] def test = [ androidRunner: "com.android.support.test:runner:${versions.androidTest}", androidRules: "com.android.support.test:rules:${versions.androidTest}", junit: 'junit:junit:4.12', - robolectric: 'org.robolectric:robolectric:3.2.2', + robolectric: 'org.robolectric:robolectric:4.0', assertj: 'org.assertj:assertj-core:1.7.1', mockito: 'org.mockito:mockito-core:1.10.19', guava: 'com.google.guava:guava:23.4-android', diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java index 19d7a58d..9ba90842 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RideRequestActivityTest.java @@ -62,7 +62,8 @@ public void setup() { Intent data = RideRequestActivity.newIntent(Robolectric.setupActivity(Activity.class), null, new SessionConfiguration.Builder().setClientId("clientId").build(), null); - activity = Robolectric.buildActivity(RideRequestActivity.class).withIntent(data).create() + activity = Robolectric.buildActivity(RideRequestActivity.class, data) + .create() .get(); } @@ -115,7 +116,7 @@ public void onLoad_whenNullUserAgent_shouldAddRideWidgetUserAgent() { rideParameters, new SessionConfiguration.Builder().setClientId("clientId").build(), null); - activity = Robolectric.buildActivity(RideRequestActivity.class).withIntent(data).create().get(); + activity = Robolectric.buildActivity(RideRequestActivity.class, data).create().get(); String tokenString = "accessToken1234"; AccessToken accessToken = new AccessToken(2592000, ImmutableList.of(Scope.RIDE_WIDGETS), tokenString, @@ -135,7 +136,7 @@ public void onLoad_withUserAgentInRideParametersButton_shouldNotGetOverridden() rideParameters, new SessionConfiguration.Builder().setClientId("clientId").build(), null); - activity = Robolectric.buildActivity(RideRequestActivity.class).withIntent(data).create().get(); + activity = Robolectric.buildActivity(RideRequestActivity.class, data).create().get(); assertEquals(userAgent, activity.rideRequestView.rideParameters.getUserAgent()); } @@ -144,7 +145,7 @@ public void onCreate_withNullRideParameters_shouldCreateDefaultParamsAndLoad() { ShadowActivity shadowActivity = shadowOf(activity); Intent intent = new Intent(); intent.putExtra(RideRequestActivity.EXTRA_LOGIN_CONFIGURATION, new SessionConfiguration.Builder().setClientId("clientId").build()); - activity = Robolectric.buildActivity(RideRequestActivity.class).withIntent(intent).create().get(); + activity = Robolectric.buildActivity(RideRequestActivity.class, intent).create().get(); assertNull(shadowActivity.getResultIntent()); assertFalse(shadowActivity.isFinishing()); } diff --git a/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java b/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java index b883522b..dabb5866 100644 --- a/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java +++ b/rides-android/src/test/java/com/uber/sdk/android/rides/RobolectricTestBase.java @@ -30,6 +30,6 @@ * Base test class to remove use of annotations on all test classes. */ @RunWith(RobolectricTestRunner.class) -@Config(constants = BuildConfig.class, sdk = 21) +@Config(sdk = 21) public abstract class RobolectricTestBase { } From 701154bf1b516fabf0672ba047827d27bfad83cd Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Thu, 12 Aug 2021 14:22:00 -0700 Subject: [PATCH 126/165] restore workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55f7f286..8b69c107 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: runs-on: macOS-latest # enables hardware acceleration in the virtual machine, required for emulator testing strategy: matrix: - api-level: [ 29, 30 ] + api-level: [ 21, 23, 26, 29, 30 ] steps: - name: Checkout uses: actions/checkout@v2 From 74af3f9b16dfc0ff67800ebdfe865c7580046ac5 Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Thu, 12 Aug 2021 14:45:19 -0700 Subject: [PATCH 127/165] Update RideRequestActivity.java Retry --- .../java/com/uber/sdk/android/rides/RideRequestActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java index 19628edc..f2ab1a5b 100644 --- a/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java +++ b/rides-android/src/main/java/com/uber/sdk/android/rides/RideRequestActivity.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; From 2e2bbc832ddd755ac733abc916f985d30563c11d Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Tue, 17 Aug 2021 16:34:05 -0700 Subject: [PATCH 128/165] test --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b69c107..63a76f7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: runs-on: macOS-latest # enables hardware acceleration in the virtual machine, required for emulator testing strategy: matrix: - api-level: [ 21, 23, 26, 29, 30 ] + api-level: [ 21, 23, 26 ] steps: - name: Checkout uses: actions/checkout@v2 From 233abbb9ca1f1a29381c7279f90ee0ec10b82de2 Mon Sep 17 00:00:00 2001 From: Edbert Chan Date: Thu, 19 Aug 2021 14:24:01 -0700 Subject: [PATCH 129/165] [Gradle Release Plugin] - pre tag commit: 'v0.10.3'. --- CHANGELOG.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee246e73..185849f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.10.3 - TBD +v0.10.3 - 08/19/2021 ------------- v0.10.2 - 12/03/2019 diff --git a/gradle.properties b/gradle.properties index 751eb561..d7ce2d91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -#Tue, 03 Dec 2019 14:16:24 -0800 +#Thu, 19 Aug 2021 14:13:46 -0700 GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.3-SNAPSHOT -VERSION_NAME=0.10.3-SNAPSHOT +version=0.10.3 +VERSION_NAME=0.10.3 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 73b25c399f430f3e4e3e1b87490fb60746859b43 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 25 Apr 2023 01:14:18 -0700 Subject: [PATCH 130/165] Added support for login par flow --- core-android/build.gradle | 5 +- .../uber/sdk/android/core/auth/AuthUtils.java | 9 +- .../sdk/android/core/auth/LoginActivity.java | 188 ++++++++++++++---- .../sdk/android/core/auth/LoginManager.java | 43 +++- .../android/core/auth/LoginPARDispatcher.java | 27 +++ core-android/src/main/res/values/dimens.xml | 1 + gradle/dependencies.gradle | 14 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 8 files changed, 236 insertions(+), 55 deletions(-) create mode 100644 core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java diff --git a/core-android/build.gradle b/core-android/build.gradle index f006e1ae..a7061ea8 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -41,8 +41,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } testOptions { @@ -60,6 +60,7 @@ dependencies { implementation deps.support.appCompat implementation deps.support.annotations implementation deps.support.chrometabs + implementation deps.network.moshi testImplementation deps.test.junit testImplementation deps.test.assertj diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index 9f96f20b..19ac1787 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -258,10 +258,11 @@ static String createEncodedParam(String rawParam) { static String buildUrl( @NonNull String redirectUri, @NonNull ResponseType responseType, - @NonNull SessionConfiguration configuration) { + @NonNull SessionConfiguration configuration, + @NonNull String requestUri) { final String CLIENT_ID_PARAM = "client_id"; - final String ENDPOINT = "login"; + final String ENDPOINT = "auth"; final String HTTPS = "https"; final String PATH = "oauth/v2/authorize"; final String REDIRECT_PARAM = "redirect_uri"; @@ -269,6 +270,7 @@ static String buildUrl( final String SCOPE_PARAM = "scope"; final String SHOW_FB_PARAM = "show_fb"; final String SIGNUP_PARAMS = "signup_params"; + final String REQUEST_URI_PARAM = "request_uri"; final String REDIRECT_LOGIN = "{\"redirect_to_login\":true}"; @@ -284,6 +286,9 @@ static String buildUrl( .appendQueryParameter(SCOPE_PARAM, getScopes(configuration)) .appendQueryParameter(SHOW_FB_PARAM, "false") .appendQueryParameter(SIGNUP_PARAMS, AuthUtils.createEncodedParam(REDIRECT_LOGIN)); + if (!TextUtils.isEmpty(requestUri)) { + builder.appendQueryParameter(REQUEST_URI_PARAM, requestUri); + } return builder.build().toString(); } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 6ccdfa1f..42a3ebb2 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -33,11 +33,15 @@ import androidx.annotation.VisibleForTesting; import androidx.browser.customtabs.CustomTabsIntent; import android.text.TextUtils; +import android.view.Gravity; +import android.view.ViewGroup; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; +import android.widget.LinearLayout; +import android.widget.ProgressBar; import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.R; @@ -45,6 +49,8 @@ import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; +import com.uber.sdk.core.client.internal.LoginPARRequest; +import com.uber.sdk.core.client.internal.LoginPARRequestException; import java.util.ArrayList; @@ -62,6 +68,10 @@ public class LoginActivity extends Activity { static final String EXTRA_SSO_ENABLED = "SSO_ENABLED"; static final String EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED = "REDIRECT_TO_PLAY_STORE_ENABLED"; + static final String EXTRA_REQUEST_URI = "REQUEST_URI"; + + static final String EXTRA_PAR_FLOW = "PAR_FLOW"; + static final String ERROR = "error"; private ArrayList productPriority; @@ -78,13 +88,16 @@ public class LoginActivity extends Activity { @VisibleForTesting CustomTabsHelper customTabsHelper = new CustomTabsHelper(); + @VisibleForTesting + LinearLayout progressBarLayoutContainer; + /** * Create an {@link Intent} to pass to this activity * - * @param context the {@link Context} for the intent + * @param context the {@link Context} for the intent * @param sessionConfiguration to be used for gather clientId - * @param responseType that is expected + * @param responseType that is expected * @return an intent that can be passed to this activity */ @NonNull @@ -93,16 +106,16 @@ public static Intent newIntent( @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType) { - return newIntent(context, sessionConfiguration, responseType, false); + return newIntent(context, sessionConfiguration, responseType, ""); } /** * Create an {@link Intent} to pass to this activity * - * @param context the {@link Context} for the intent + * @param context the {@link Context} for the intent * @param sessionConfiguration to be used for gather clientId - * @param responseType that is expected - * @param forceWebview Forced to use old webview instead of chrometabs + * @param responseType that is expected + * @param isParFlow specifies whether to send user's profile info to Uber as part of login/signup flow * @return an intent that can be passed to this activity */ @NonNull @@ -110,20 +123,54 @@ public static Intent newIntent( @NonNull Context context, @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType, - boolean forceWebview) { - - return newIntent(context, new ArrayList(), sessionConfiguration, responseType, forceWebview, false, false); + boolean isParFlow) { + + return newIntent(context, + new ArrayList(), + sessionConfiguration, + responseType, + "", + false, + false, + isParFlow); } /** * Create an {@link Intent} to pass to this activity * - * @param context the {@link Context} for the intent - * @param productPriority dictates the order of which Uber applications should be used for SSO. + * @param context the {@link Context} for the intent * @param sessionConfiguration to be used for gather clientId - * @param responseType that is expected - * @param forceWebview Forced to use old webview instead of chrometabs - * @param isSsoEnabled specifies whether to attempt login with SSO + * @param responseType that is expected + * @param requestUri to be used to prefill user's profile hint information + * @return an intent that can be passed to this activity + */ + @NonNull + public static Intent newIntent( + @NonNull Context context, + @NonNull SessionConfiguration sessionConfiguration, + @NonNull ResponseType responseType, + @NonNull String requestUri) { + + return newIntent(context, + new ArrayList(), + sessionConfiguration, + responseType, + requestUri, + false, + false, + false); + } + + /** + * Create an {@link Intent} to pass to this activity + * + * @param context the {@link Context} for the intent + * @param productPriority dictates the order of which Uber applications should be used for SSO. + * @param sessionConfiguration to be used for gather clientId + * @param responseType that is expected + * @param isSsoEnabled specifies whether to attempt login with SSO + * @param isRedirectToPlayStoreEnabled specifies whether to redirect to Play Store if Uber app is not installed + * @param isParFlow specifies whether to send user's profile info to Uber as part of login/signup flow * @return an intent that can be passed to this activity */ @NonNull @@ -132,17 +179,19 @@ static Intent newIntent( @NonNull ArrayList productPriority, @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType, - boolean forceWebview, + String requestUri, boolean isSsoEnabled, - boolean isRedirectToPlayStoreEnabled) { + boolean isRedirectToPlayStoreEnabled, + boolean isParFlow) { final Intent data = new Intent(context, LoginActivity.class) .putExtra(EXTRA_PRODUCT_PRIORITY, productPriority) .putExtra(EXTRA_SESSION_CONFIGURATION, sessionConfiguration) + .putExtra(EXTRA_REQUEST_URI, requestUri) .putExtra(EXTRA_RESPONSE_TYPE, responseType) - .putExtra(EXTRA_FORCE_WEBVIEW, forceWebview) .putExtra(EXTRA_SSO_ENABLED, isSsoEnabled) - .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled); + .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled) + .putExtra(EXTRA_PAR_FLOW, isParFlow); return data; } @@ -171,8 +220,8 @@ protected void onCreate(Bundle savedInstanceState) { protected void onResume() { super.onResume(); - if(webView == null) { - if(!authStarted) { + if (webView == null) { + if (!authStarted) { authStarted = true; return; } @@ -190,10 +239,21 @@ protected void onNewIntent(Intent intent) { } protected void init() { - if(getIntent().getData() != null) { + if (getIntent().getData() != null) { handleResponse(getIntent().getData()); } else { - loadUrl(); + sessionConfiguration = (SessionConfiguration) getIntent().getSerializableExtra(EXTRA_SESSION_CONFIGURATION); + responseType = (ResponseType) getIntent().getSerializableExtra(EXTRA_RESPONSE_TYPE); + if (isParFlow(getIntent())) { + addProgressIndicator(); + LoginPARDispatcher.dispatchPAR( + sessionConfiguration, + responseType, + new LoginPARCallback(responseType) + ); + } else { + loadUrl(); + } } } @@ -228,25 +288,21 @@ protected void loadUrl() { return; } - boolean forceWebview = intent.getBooleanExtra(EXTRA_FORCE_WEBVIEW, false); + String requestUri = intent.getStringExtra(EXTRA_REQUEST_URI); boolean isRedirectToPlayStoreEnabled = intent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false); if (responseType == ResponseType.CODE) { - loadWebPage(redirectUri, ResponseType.CODE, sessionConfiguration, forceWebview); + loadWebPage(redirectUri, ResponseType.CODE, sessionConfiguration, requestUri); } else if (responseType == ResponseType.TOKEN && !(AuthUtils.isPrivilegeScopeRequired(sessionConfiguration.getScopes()) && isRedirectToPlayStoreEnabled)) { - loadWebPage(redirectUri, ResponseType.TOKEN, sessionConfiguration, forceWebview); + loadWebPage(redirectUri, ResponseType.TOKEN, sessionConfiguration, requestUri); } else { redirectToInstallApp(this); } } - protected void loadWebPage(String redirectUri, ResponseType responseType, SessionConfiguration sessionConfiguration, boolean forceWebview) { - String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration); - if (forceWebview) { - loadWebview(url, redirectUri); - } else { - loadChrometab(url); - } + protected void loadWebPage(String redirectUri, ResponseType responseType, SessionConfiguration sessionConfiguration, String requestUri) { + String url = AuthUtils.buildUrl(redirectUri, responseType, sessionConfiguration, requestUri); + loadChrometab(url); } /** @@ -347,7 +403,7 @@ private boolean validateRequestParams() { } if ((sessionConfiguration.getScopes() == null || sessionConfiguration.getScopes().isEmpty()) - && (sessionConfiguration.getCustomScopes() == null || sessionConfiguration.getCustomScopes().isEmpty())) { + && (sessionConfiguration.getCustomScopes() == null || sessionConfiguration.getCustomScopes().isEmpty())) { onError(AuthenticationError.INVALID_SCOPE); return false; } @@ -364,6 +420,42 @@ private void redirectToInstallApp(@NonNull Activity activity) { new SignupDeeplink(activity, sessionConfiguration.getClientId(), USER_AGENT).execute(); } + private boolean isParFlow(Intent intent) { + return intent.getBooleanExtra(EXTRA_PAR_FLOW, false); + } + + private void addProgressIndicator() { + progressBarLayoutContainer = new LinearLayout(this); + progressBarLayoutContainer.setGravity(Gravity.CENTER); + progressBarLayoutContainer.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT + ) + ); + ProgressBar progressBar = new ProgressBar(this); + progressBar.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + (int) getResources().getDimension(R.dimen.ub__progress_bar_height) + ) + ); + ViewGroup rootLayout = findViewById(android.R.id.content); + progressBarLayoutContainer.addView(progressBar); + rootLayout.addView(progressBarLayoutContainer); + } + + private void removeProgressBar() { + ViewGroup rootLayout = findViewById(android.R.id.content); + if (progressBarLayoutContainer.getParent() != null) { + ((ViewGroup) progressBarLayoutContainer.getParent()).removeView(progressBarLayoutContainer); + } + } + + private void loginInternal(String requestUri) { + getIntent().putExtra(EXTRA_REQUEST_URI, requestUri); + loadUrl(); + } + /** * Custom {@link WebViewClient} for authorization. */ @@ -384,6 +476,7 @@ public OAuthWebViewClient(@NonNull String redirectUri) { /** * add deprecated member "onReceivedError" to solve compatibility issue when API level < 23 + * * @param view * @param errorCode * @param description @@ -407,7 +500,7 @@ public void onReceivedHttpError(WebView view, WebResourceRequest request, WebRes receivedError(); } - private void receivedError(){ + private void receivedError() { onError(AuthenticationError.CONNECTIVITY_ISSUE); } } @@ -453,4 +546,31 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { return super.shouldOverrideUrlLoading(view, url); } } + + class LoginPARCallback implements LoginPARRequest.Callback { + + private final ResponseType responseType; + + LoginPARCallback(ResponseType responseType) { + this.responseType = responseType; + } + + @Override + public void onSuccess(String requestUri) { + removeProgressBar(); + loginInternal(requestUri); + } + + private void removeProgressBar() { + if (progressBarLayoutContainer.getParent() != null) { + ((ViewGroup) progressBarLayoutContainer.getParent()).removeView(progressBarLayoutContainer); + } + } + + @Override + public void onError(LoginPARRequestException e) { + removeProgressBar(); + LoginActivity.this.onError(AuthenticationError.INVALID_FLOW_ERROR); + } + } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index ca26daab..9a5e8f32 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -27,24 +27,42 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; + import android.util.Log; +import android.view.Gravity; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ProgressBar; + +import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; +import com.uber.sdk.core.auth.internal.OAuth2Service; +import com.uber.sdk.core.auth.internal.ProfileHint; import com.uber.sdk.core.client.AccessTokenSession; import com.uber.sdk.core.client.ServerTokenSession; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; +import com.uber.sdk.core.client.internal.LoginPARRequest; +import com.uber.sdk.core.client.internal.LoginPARRequestException; + +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.Executors; import static com.uber.sdk.android.core.utils.Preconditions.checkState; import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; +import retrofit2.Retrofit; +import retrofit2.converter.moshi.MoshiConverterFactory; + /** * Manages user login via OAuth 2.0 Implicit Grant. Be sure to call * {@link LoginManager#onActivityResult(Activity, int, int, Intent)} in your @@ -100,6 +118,8 @@ public class LoginManager { @Deprecated private boolean redirectForAuthorizationCode = false; + private LinearLayout progressBarLayoutContainer; + /** * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} @@ -182,9 +202,10 @@ public void login(final @NonNull Activity activity) { productFlowPriority, sessionConfiguration, ResponseType.TOKEN, - false, + "", + true, true, - true); + false); activity.startActivityForResult(intent, requestCode); } else if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.DEFAULT)) { ssoDeeplink.execute(SsoDeeplink.FlowVersion.DEFAULT); @@ -206,10 +227,7 @@ public void loginForImplicitGrant(@NonNull Activity activity) { if (!legacyUriRedirectHandler.checkValidState(activity, this)) { return; } - - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, - ResponseType.TOKEN, legacyUriRedirectHandler.isLegacyMode()); - activity.startActivityForResult(intent, requestCode); + executePARRequestIfNecessary(activity, ResponseType.TOKEN); } /** @@ -222,9 +240,7 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { return; } - Intent intent = LoginActivity.newIntent(activity, sessionConfiguration, - ResponseType.CODE, legacyUriRedirectHandler.isLegacyMode()); - activity.startActivityForResult(intent, requestCode); + executePARRequestIfNecessary(activity, ResponseType.CODE); } /** @@ -238,12 +254,17 @@ private void loginForImplicitGrantWithFallback(@NonNull Activity activity) { return; } + executePARRequestIfNecessary(activity, ResponseType.CODE); + } + + private void executePARRequestIfNecessary(Activity activity, ResponseType responseType) { Intent intent = LoginActivity.newIntent( activity, - new ArrayList(), + productFlowPriority, sessionConfiguration, ResponseType.TOKEN, - legacyUriRedirectHandler.isLegacyMode(), + "", + false, false, true); activity.startActivityForResult(intent, requestCode); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java new file mode 100644 index 00000000..4ae2a002 --- /dev/null +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java @@ -0,0 +1,27 @@ +package com.uber.sdk.android.core.auth; + +import com.uber.sdk.core.auth.internal.OAuth2Service; +import com.uber.sdk.core.client.SessionConfiguration; +import com.uber.sdk.core.client.internal.LoginPARRequest; + +import retrofit2.Retrofit; +import retrofit2.converter.moshi.MoshiConverterFactory; + +public class LoginPARDispatcher { + + public static void dispatchPAR(SessionConfiguration sessionConfiguration, + ResponseType responseType, + LoginActivity.LoginPARCallback callback) { + new LoginPARRequest( + new Retrofit.Builder() + .baseUrl(sessionConfiguration.getLoginHost()) + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(OAuth2Service.class), + sessionConfiguration.getProfileHint(), + sessionConfiguration.getClientId(), + responseType.name(), + callback + ).executePAR(); + } +} diff --git a/core-android/src/main/res/values/dimens.xml b/core-android/src/main/res/values/dimens.xml index 121acb14..435edd3b 100644 --- a/core-android/src/main/res/values/dimens.xml +++ b/core-android/src/main/res/values/dimens.xml @@ -28,4 +28,5 @@ 8dp 48dp + 32dp diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 09182ae4..10633d09 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -17,7 +17,11 @@ def versions = [ androidTest: '0.5', androidxVersion: '1.0.0', - uberJava: '0.8.0', + uberJava: '0.8.1-SNAPSHOT', +] + +def network = [ + moshi: 'com.squareup.moshi:moshi:1.9.2', ] def build = [ @@ -25,7 +29,7 @@ def build = [ buildToolsVersion: '28.0.2', compileSdkVersion: 28, ci: 'true' == System.getenv('CI'), - minSdkVersion: 15, + minSdkVersion: 16, targetSdkVersion: 28, repositories: [ @@ -33,10 +37,11 @@ def build = [ ], gradlePlugins: [ - android: 'com.android.tools.build:gradle:3.2.0', + android: 'com.android.tools.build:gradle:3.3.2', release: 'net.researchgate:gradle-release:2.1.2', github: 'co.riiid:gradle-github-plugin:0.4.2', cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1', + buildConfig: 'gradle.plugin.de.fuerstenau:BuildConfigPlugin:1.1.8' ] ] @@ -72,5 +77,6 @@ ext.deps = [ "support": support, "test": test, "versions": versions, - "uber": uber + "uber": uber, + "network": network ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bd24854f..8b7001e6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +zipStoreBase=GRADLE_USER_HOME From 0d94f02f1013b684c461f2dea2e811879681cc0a Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 25 Apr 2023 01:41:33 -0700 Subject: [PATCH 131/165] changed executeParIfNeccessary method name to launchOnboardingFlow --- .../sdk/android/core/auth/LoginManager.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 9a5e8f32..cf3a9946 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -29,40 +29,26 @@ import androidx.annotation.VisibleForTesting; import android.util.Log; -import android.view.Gravity; -import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.ProgressBar; -import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.UberSdk; import com.uber.sdk.android.core.utils.AppProtocol; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; import com.uber.sdk.core.auth.Scope; -import com.uber.sdk.core.auth.internal.OAuth2Service; -import com.uber.sdk.core.auth.internal.ProfileHint; import com.uber.sdk.core.client.AccessTokenSession; import com.uber.sdk.core.client.ServerTokenSession; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; -import com.uber.sdk.core.client.internal.LoginPARRequest; -import com.uber.sdk.core.client.internal.LoginPARRequestException; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; -import java.util.concurrent.Executors; import static com.uber.sdk.android.core.utils.Preconditions.checkState; import static com.uber.sdk.core.client.utils.Preconditions.checkNotNull; -import retrofit2.Retrofit; -import retrofit2.converter.moshi.MoshiConverterFactory; - /** * Manages user login via OAuth 2.0 Implicit Grant. Be sure to call * {@link LoginManager#onActivityResult(Activity, int, int, Intent)} in your @@ -227,7 +213,7 @@ public void loginForImplicitGrant(@NonNull Activity activity) { if (!legacyUriRedirectHandler.checkValidState(activity, this)) { return; } - executePARRequestIfNecessary(activity, ResponseType.TOKEN); + launchOnboardingFlow(activity, ResponseType.TOKEN); } /** @@ -240,7 +226,7 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { return; } - executePARRequestIfNecessary(activity, ResponseType.CODE); + launchOnboardingFlow(activity, ResponseType.CODE); } /** @@ -254,10 +240,10 @@ private void loginForImplicitGrantWithFallback(@NonNull Activity activity) { return; } - executePARRequestIfNecessary(activity, ResponseType.CODE); + launchOnboardingFlow(activity, ResponseType.CODE); } - private void executePARRequestIfNecessary(Activity activity, ResponseType responseType) { + private void launchOnboardingFlow(Activity activity, ResponseType responseType) { Intent intent = LoginActivity.newIntent( activity, productFlowPriority, From 04b4a317c99b18fb375d8768dd3ffbd3b0092cb4 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 9 May 2023 15:29:07 -0700 Subject: [PATCH 132/165] updated rides java sdk version and fixed unit tests --- build.gradle | 2 +- core-android/build.gradle | 3 +- .../sdk/android/core/auth/LoginActivity.java | 6 +- .../sdk/android/core/auth/LoginManager.java | 15 +++-- .../android/core/auth/LoginPARDispatcher.java | 6 +- .../sdk/android/core/auth/AuthUtilsTest.java | 4 +- .../android/core/auth/LoginActivityTest.java | 65 ++++--------------- .../android/core/auth/LoginManagerTest.java | 16 ++--- gradle/dependencies.gradle | 2 +- gradle/verification.gradle | 4 +- rides-android/build.gradle | 2 +- samples/login-sample/build.gradle | 2 +- .../android/samples/LoginSampleActivity.java | 11 +++- samples/request-button-sample/build.gradle | 2 +- 14 files changed, 52 insertions(+), 88 deletions(-) diff --git a/build.gradle b/build.gradle index afc8b43f..5a459617 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() maven { url deps.build.repositories.plugins } } dependencies { diff --git a/core-android/build.gradle b/core-android/build.gradle index a7061ea8..3a7bc3c5 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -17,7 +17,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() maven { url deps.build.repositories.plugins } } @@ -66,6 +66,7 @@ dependencies { testImplementation deps.test.assertj testImplementation deps.test.mockito testImplementation deps.test.robolectric + testImplementation project(path: ':core-android') } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') \ No newline at end of file diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 42a3ebb2..bd112beb 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -49,8 +49,8 @@ import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.client.SessionConfiguration; -import com.uber.sdk.core.client.internal.LoginPARRequest; import com.uber.sdk.core.client.internal.LoginPARRequestException; +import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; import java.util.ArrayList; @@ -266,8 +266,6 @@ protected void onDestroy() { protected void loadUrl() { Intent intent = getIntent(); - sessionConfiguration = (SessionConfiguration) intent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION); - responseType = (ResponseType) intent.getSerializableExtra(EXTRA_RESPONSE_TYPE); productPriority = (ArrayList) intent.getSerializableExtra(EXTRA_PRODUCT_PRIORITY); if (!validateRequestParams()) { @@ -547,7 +545,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { } } - class LoginPARCallback implements LoginPARRequest.Callback { + class LoginPARCallback implements LoginPushedAuthorizationRequest.Callback { private final ResponseType responseType; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index cf3a9946..dbeca7fc 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -175,7 +175,6 @@ public void login(final @NonNull Activity activity) { checkState(hasScopes, "Scopes must be set in the Session Configuration."); checkNotNull(sessionConfiguration.getRedirectUri(), "Redirect URI must be set in Session Configuration."); - if (!legacyUriRedirectHandler.checkValidState(activity, this)) { return; } @@ -213,7 +212,7 @@ public void loginForImplicitGrant(@NonNull Activity activity) { if (!legacyUriRedirectHandler.checkValidState(activity, this)) { return; } - launchOnboardingFlow(activity, ResponseType.TOKEN); + launchOnboardingFlow(activity, ResponseType.TOKEN, false); } /** @@ -226,7 +225,7 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { return; } - launchOnboardingFlow(activity, ResponseType.CODE); + launchOnboardingFlow(activity, ResponseType.CODE, false); } /** @@ -240,18 +239,20 @@ private void loginForImplicitGrantWithFallback(@NonNull Activity activity) { return; } - launchOnboardingFlow(activity, ResponseType.CODE); + launchOnboardingFlow(activity, ResponseType.TOKEN, true); } - private void launchOnboardingFlow(Activity activity, ResponseType responseType) { + private void launchOnboardingFlow(Activity activity, + ResponseType responseType, + boolean isRedirectToPlayStoreEnabled) { Intent intent = LoginActivity.newIntent( activity, productFlowPriority, sessionConfiguration, - ResponseType.TOKEN, + responseType, "", false, - false, + isRedirectToPlayStoreEnabled, true); activity.startActivityForResult(intent, requestCode); } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java index 4ae2a002..0f71e806 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java @@ -2,7 +2,7 @@ import com.uber.sdk.core.auth.internal.OAuth2Service; import com.uber.sdk.core.client.SessionConfiguration; -import com.uber.sdk.core.client.internal.LoginPARRequest; +import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; import retrofit2.Retrofit; import retrofit2.converter.moshi.MoshiConverterFactory; @@ -12,7 +12,7 @@ public class LoginPARDispatcher { public static void dispatchPAR(SessionConfiguration sessionConfiguration, ResponseType responseType, LoginActivity.LoginPARCallback callback) { - new LoginPARRequest( + new LoginPushedAuthorizationRequest( new Retrofit.Builder() .baseUrl(sessionConfiguration.getLoginHost()) .addConverterFactory(MoshiConverterFactory.create()) @@ -22,6 +22,6 @@ public static void dispatchPAR(SessionConfiguration sessionConfiguration, sessionConfiguration.getClientId(), responseType.name(), callback - ).executePAR(); + ).execute(); } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index 0f68f4a8..068b987e 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -286,8 +286,8 @@ public void onBuildUrl_withDefaultRegion_shouldHaveDefaultUberDomain() { .setClientId(clientId) .build(); - String url = AuthUtils.buildUrl(redirectUri, ResponseType.TOKEN, loginConfiguration); - assertEquals("https://login.uber.com/oauth/v2/authorize?client_id=" + clientId + + String url = AuthUtils.buildUrl(redirectUri, ResponseType.TOKEN, loginConfiguration, ""); + assertEquals("https://auth.uber.com/oauth/v2/authorize?client_id=" + clientId + "&redirect_uri=" + redirectUri + "&response_type=token&scope=history&" + "show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A", url); } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index 295f87cf..574b96ce 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -150,7 +150,7 @@ public void onLoginLoad_withNullResponseType_shouldReturnErrorResultIntent() { @Test public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, - loginConfiguration, ResponseType.TOKEN, false, true, true); + loginConfiguration, ResponseType.TOKEN, "", true, true, false); ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); @@ -166,7 +166,7 @@ public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { @Test public void onLoginLoad_withSsoEnabled_andNotSupported_shouldReturnErrorResultIntent() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, - loginConfiguration, ResponseType.TOKEN, false, true, true); + loginConfiguration, ResponseType.TOKEN, "", true, true, false); ActivityController controller = Robolectric.buildActivity(LoginActivity.class).newIntent(intent); loginActivity = controller.get(); @@ -184,92 +184,51 @@ public void onLoginLoad_withSsoEnabled_andNotSupported_shouldReturnErrorResultIn } @Test - public void onLoginLoad_withResponseTypeCode_andForceWebview_shouldLoadWebview() { + public void onLoginLoad_withResponseTypeCode_shouldLoadChrometab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.CODE, true); - - loginActivity = Robolectric.buildActivity(LoginActivity.class, intent).create().get(); - ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); - - String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration); - assertThat(webview.getLastLoadedUrl()).isEqualTo(expectedUrl); - } - - @Test - public void onLoginLoad_withResponseTypeCode_andNotForceWebview_shouldLoadChrometab() { - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.CODE, false); + ResponseType.CODE); ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); loginActivity.customTabsHelper = customTabsHelper; controller.create(); - String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, ""); verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); } @Test - public void onLoginLoad_withResponseTypeToken_andForceWebview_andGeneralScopes_shouldLoadWebview() { - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, true); - - loginActivity = Robolectric.buildActivity(LoginActivity.class, intent).create().get(); - ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); - - String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); - assertThat(webview.getLastLoadedUrl()).isEqualTo(expectedUrl); - } - - @Test - public void onLoginLoad_withResponseTypeToken_andNotForceWebview_andGeneralScopes_shouldLoadChrometab() { + public void onLoginLoad_withResponseTypeToken_andGeneralScopes_shouldLoadChrometab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, false); + ResponseType.TOKEN); ActivityController controller = Robolectric.buildActivity(LoginActivity.class).newIntent(intent); loginActivity = controller.get(); loginActivity.customTabsHelper = customTabsHelper; controller.create(); - String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration, ""); verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); } @Test - public void onLoginLoad_withResponseTypeToken_andForceWebview_andPrivilegedScopes_andRedirectToPlayStoreDisabled_shouldLoadWebview() { - loginConfiguration = new SessionConfiguration.Builder() - .setClientId(CLIENT_ID) - .setRedirectUri(REDIRECT_URI) - .setScopes(MIXED_SCOPES) - .build(); - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, true); - - loginActivity = Robolectric.buildActivity(LoginActivity.class, intent).create().get(); - ShadowWebView webview = Shadows.shadowOf(loginActivity.webView); - - String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); - assertThat(webview.getLastLoadedUrl()).isEqualTo(expectedUrl); - } - - @Test - public void onLoginLoad_withResponseTypeToken_andNotForceWebview_andPrivilegedScopes_andRedirectToPlayStoreDisabled_shouldLoadChrometab() { + public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToPlayStoreDisabled_shouldLoadChrometab() { loginConfiguration = new SessionConfiguration.Builder() .setClientId(CLIENT_ID) .setRedirectUri(REDIRECT_URI) .setScopes(MIXED_SCOPES) .build(); Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.TOKEN, false); + ResponseType.TOKEN); ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); loginActivity.customTabsHelper = customTabsHelper; controller.create(); - String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.TOKEN, loginConfiguration, ""); verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); } @@ -282,7 +241,7 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP .setScopes(MIXED_SCOPES) .build(); Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), - new ArrayList(), loginConfiguration, ResponseType.TOKEN, true, false, true); + new ArrayList(), loginConfiguration, ResponseType.TOKEN, "", false, true, false); ShadowActivity shadowActivity = shadowOf(Robolectric.buildActivity(LoginActivity.class, intent).create().get()); diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 66063414..94b1d76f 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -157,7 +157,7 @@ public void login_withRedirectToSdkFlowSsoSupported_shouldLoginActivityWithSsoPa final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList<>(productPriority), sessionConfiguration, - ResponseType.TOKEN, false, true, true); + ResponseType.TOKEN, true, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -176,7 +176,7 @@ public void login_withoutAppPriority_shouldLoginActivityWithSsoParams() { final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, - ResponseType.TOKEN, false, true, true); + ResponseType.TOKEN, true, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -206,7 +206,7 @@ public void login_withSsoNotSupported_andAuthCodeFlowEnabled_shouldLoginWithAuth final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, - ResponseType.CODE, false, false, false); + ResponseType.CODE, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -224,7 +224,7 @@ public void login_withSsoNotSupported_andAuthCodeFlowDisabled_shouldLoginWithImp final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, - ResponseType.TOKEN, false, false, true); + ResponseType.TOKEN, false, true); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -247,7 +247,7 @@ public void loginForImplicitGrant_withoutLegacyModeBlocking_shouldLoginWithImpli final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, - ResponseType.TOKEN, false, false, false); + ResponseType.TOKEN, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -270,7 +270,7 @@ public void loginForAuthorizationCode_withoutLegacyModeBlocking_shouldLoginWithA final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, - ResponseType.CODE, false, false, false); + ResponseType.CODE, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -301,7 +301,7 @@ public void login_whenOnlyCustomScopes_shouldLogin() { final Intent resultIntent = intentCaptor.getValue(); validateLoginIntentFields(resultIntent, new ArrayList(), sessionConfiguration, - ResponseType.CODE, false, false, false); + ResponseType.CODE, false, false); assertThat(codeCaptor.getValue()).isEqualTo(REQUEST_CODE_LOGIN_DEFAULT); } @@ -516,14 +516,12 @@ private void validateLoginIntentFields( @NonNull List expectedProductPriority, @NonNull SessionConfiguration expectedSessionConfiguration, @NonNull ResponseType expectedResponseType, - boolean expectedForceWebview, boolean expectedSsoEnabled, boolean expectedRedirectToPlayStoreEnabled) { assertThat(loginIntent.getSerializableExtra(EXTRA_SESSION_CONFIGURATION)).isEqualTo(expectedSessionConfiguration); assertThat(loginIntent.getSerializableExtra(EXTRA_RESPONSE_TYPE)).isEqualTo(expectedResponseType); assertThat((ArrayList) loginIntent.getSerializableExtra(EXTRA_PRODUCT_PRIORITY)) .containsAll(expectedProductPriority); - assertThat(loginIntent.getBooleanExtra(EXTRA_FORCE_WEBVIEW, false)).isEqualTo(expectedForceWebview); assertThat(loginIntent.getBooleanExtra(EXTRA_SSO_ENABLED, false)).isEqualTo(expectedSsoEnabled); assertThat(loginIntent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false)) .isEqualTo(expectedRedirectToPlayStoreEnabled); diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 10633d09..e73eb45d 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -17,7 +17,7 @@ def versions = [ androidTest: '0.5', androidxVersion: '1.0.0', - uberJava: '0.8.1-SNAPSHOT', + uberJava: '0.8.1', ] def network = [ diff --git a/gradle/verification.gradle b/gradle/verification.gradle index 39c9b5dc..c712c3fa 100644 --- a/gradle/verification.gradle +++ b/gradle/verification.gradle @@ -2,13 +2,13 @@ subprojects { buildscript { repositories { google() - jcenter() + mavenCentral() } } repositories { google() - jcenter() + mavenCentral() maven { url 'https://maven.google.com' } diff --git a/rides-android/build.gradle b/rides-android/build.gradle index 17fa454f..99267c46 100644 --- a/rides-android/build.gradle +++ b/rides-android/build.gradle @@ -17,7 +17,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() maven { url deps.build.repositories.plugins } } diff --git a/samples/login-sample/build.gradle b/samples/login-sample/build.gradle index 92ab2e3e..7463dfc6 100644 --- a/samples/login-sample/build.gradle +++ b/samples/login-sample/build.gradle @@ -18,7 +18,7 @@ buildscript { apply from: rootProject.file('gradle/dependencies.gradle') repositories { google() - jcenter() + mavenCentral() } dependencies { diff --git a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java index 9df846c5..ac28664a 100644 --- a/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java +++ b/samples/login-sample/src/main/java/com/uber/sdk/android/samples/LoginSampleActivity.java @@ -45,6 +45,7 @@ import com.uber.sdk.android.rides.samples.R; import com.uber.sdk.core.auth.AccessToken; import com.uber.sdk.core.auth.AccessTokenStorage; +import com.uber.sdk.core.auth.ProfileHint; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.Session; import com.uber.sdk.core.client.SessionConfiguration; @@ -84,17 +85,23 @@ public class LoginSampleActivity extends AppCompatActivity { private Button customButton; private AccessTokenStorage accessTokenStorage; private LoginManager loginManager; - private SessionConfiguration configuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); - configuration = new SessionConfiguration.Builder() + SessionConfiguration configuration = new SessionConfiguration.Builder() .setClientId(CLIENT_ID) .setRedirectUri(REDIRECT_URI) .setScopes(Arrays.asList(Scope.PROFILE, Scope.RIDE_WIDGETS)) + .setProfileHint(new ProfileHint + .Builder() + .email("john@doe.com") + .firstName("John") + .lastName("Doe") + .phone("1234567890") + .build()) .build(); validateConfiguration(configuration); diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle index d75ac96a..013c8df0 100644 --- a/samples/request-button-sample/build.gradle +++ b/samples/request-button-sample/build.gradle @@ -18,7 +18,7 @@ buildscript { apply from: rootProject.file('gradle/dependencies.gradle') repositories { google() - jcenter() + mavenCentral() } dependencies { From 1bc2e7e7370fb0a21fa1df2ae32f1d57812c2126 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 9 May 2023 22:33:37 -0700 Subject: [PATCH 133/165] added unit tests --- .../sdk/android/core/auth/LoginActivity.java | 28 +++--- .../sdk/android/core/auth/LoginManager.java | 3 +- .../sdk/android/core/auth/AuthUtilsTest.java | 18 ++++ .../android/core/auth/LoginActivityTest.java | 94 ++++++++++++++++++- .../android/core/auth/LoginManagerTest.java | 20 ++++ 5 files changed, 147 insertions(+), 16 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index bd112beb..839938b1 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -285,7 +285,6 @@ protected void loadUrl() { } return; } - String requestUri = intent.getStringExtra(EXTRA_REQUEST_URI); boolean isRedirectToPlayStoreEnabled = intent.getBooleanExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, false); if (responseType == ResponseType.CODE) { @@ -422,8 +421,14 @@ private boolean isParFlow(Intent intent) { return intent.getBooleanExtra(EXTRA_PAR_FLOW, false); } - private void addProgressIndicator() { + /** + * Adds progress spinner to the top of the activity + */ + @VisibleForTesting + void addProgressIndicator() { progressBarLayoutContainer = new LinearLayout(this); + progressBarLayoutContainer.setId(R.id.progress_spinner); + progressBarLayoutContainer.setGravity(Gravity.CENTER); progressBarLayoutContainer.setLayoutParams( new LinearLayout.LayoutParams( @@ -442,10 +447,13 @@ private void addProgressIndicator() { rootLayout.addView(progressBarLayoutContainer); } - private void removeProgressBar() { - ViewGroup rootLayout = findViewById(android.R.id.content); + /** + * Removes progress spinner from the activity + */ + @VisibleForTesting void removeProgressIndicator() { if (progressBarLayoutContainer.getParent() != null) { ((ViewGroup) progressBarLayoutContainer.getParent()).removeView(progressBarLayoutContainer); + progressBarLayoutContainer = null; } } @@ -555,20 +563,14 @@ class LoginPARCallback implements LoginPushedAuthorizationRequest.Callback { @Override public void onSuccess(String requestUri) { - removeProgressBar(); + removeProgressIndicator(); loginInternal(requestUri); } - private void removeProgressBar() { - if (progressBarLayoutContainer.getParent() != null) { - ((ViewGroup) progressBarLayoutContainer.getParent()).removeView(progressBarLayoutContainer); - } - } - @Override public void onError(LoginPARRequestException e) { - removeProgressBar(); - LoginActivity.this.onError(AuthenticationError.INVALID_FLOW_ERROR); + removeProgressIndicator(); + loginInternal(""); } } } diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index dbeca7fc..c14ac48b 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -234,7 +234,8 @@ public void loginForAuthorizationCode(@NonNull Activity activity) { * * @param activity to start Activity on. */ - private void loginForImplicitGrantWithFallback(@NonNull Activity activity) { + @VisibleForTesting + void loginForImplicitGrantWithFallback(@NonNull Activity activity) { if (!legacyUriRedirectHandler.checkValidState(activity, this)) { return; } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java index 068b987e..524f2624 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/AuthUtilsTest.java @@ -291,4 +291,22 @@ public void onBuildUrl_withDefaultRegion_shouldHaveDefaultUberDomain() { "&redirect_uri=" + redirectUri + "&response_type=token&scope=history&" + "show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A", url); } + + @Test + public void onBuildUrl_whenRequestUriIsNotEmpty_shouldHaveRequestUriInQueryParams() { + String clientId = "clientId1234"; + String redirectUri = "localHost1234"; + + SessionConfiguration loginConfiguration = new SessionConfiguration.Builder() + .setRedirectUri(redirectUri) + .setScopes(Arrays.asList(Scope.HISTORY)) + .setClientId(clientId) + .build(); + + String url = AuthUtils.buildUrl(redirectUri, ResponseType.TOKEN, loginConfiguration, "requestUri"); + assertEquals("https://auth.uber.com/oauth/v2/authorize?client_id=" + clientId + + "&redirect_uri=" + redirectUri + "&response_type=token&scope=history&" + + "show_fb=false&signup_params=eyJyZWRpcmVjdF90b19sb2dpbiI6dHJ1ZX0%3D%0A" + + "&request_uri=requestUri", url); + } } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index 574b96ce..c3052c31 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -25,6 +25,8 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; +import android.widget.LinearLayout; import androidx.browser.customtabs.CustomTabsIntent; import com.google.common.collect.ImmutableList; @@ -33,17 +35,19 @@ import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.utils.CustomTabsHelper; import com.uber.sdk.core.auth.AccessToken; +import com.uber.sdk.core.auth.ProfileHint; import com.uber.sdk.core.auth.Scope; import com.uber.sdk.core.client.SessionConfiguration; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.Robolectric; -import org.robolectric.Shadows; +import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowActivity; -import org.robolectric.shadows.ShadowWebView; import java.util.ArrayList; import java.util.Set; @@ -60,6 +64,7 @@ /** * Tests {@link LoginActivity} */ + public class LoginActivityTest extends RobolectricTestBase { private static final String REDIRECT_URI = "localHost1234"; @@ -249,6 +254,78 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP assertThat(signupDeeplinkIntent.getData().toString()).isEqualTo(SIGNUP_DEEPLINK_URL); } + @Test + @Config(shadows = ShadowLoginPARDispatcher.class ) + public void onLoginLoad_whenPARFlowEnabled_shouldAddProgressIndicator_andLoadCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), + loginConfiguration + .newBuilder() + .setProfileHint(new ProfileHint + .Builder() + .firstName("test") + .build()) + .build(), + ResponseType.CODE, true); + + ActivityController controller = Robolectric + .buildActivity(LoginActivity.class, intent); + LoginActivity loginActivity = spy(controller.get()); + loginActivity.customTabsHelper = customTabsHelper; + loginActivity.onCreate(new Bundle()); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, "requestUri"); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + @Config(shadows = ShadowLoginPARDispatcherWithError.class ) + public void onLoginLoad_whenPARFlowEnabled_andErrorResponse_shouldAddProgressIndicator_andLoadCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.CODE, true); + + ActivityController controller = Robolectric + .buildActivity(LoginActivity.class, intent); + LoginActivity loginActivity = spy(controller.get()); + loginActivity.customTabsHelper = customTabsHelper; + loginActivity.onCreate(new Bundle()); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, ""); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + public void onLoginLoad_whenPARFlowEnabled_andProfileHintEmpty_shouldAddProgressIndicator_andLoadCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.CODE, true); + + ActivityController controller = Robolectric + .buildActivity(LoginActivity.class, intent); + loginActivity = spy(controller.get()); + loginActivity.customTabsHelper = customTabsHelper; + loginActivity.onCreate(new Bundle()); + verify(loginActivity).addProgressIndicator(); + verify(loginActivity).removeProgressIndicator(); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, ""); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + public void onLoginLoad_whenPARFlowDisabled_shouldNotAddProgressIndicator_andLoadCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, + ResponseType.CODE, false); + + ActivityController controller = Robolectric + .buildActivity(LoginActivity.class, intent); + loginActivity = spy(controller.get()); + loginActivity.customTabsHelper = customTabsHelper; + loginActivity.onCreate(new Bundle()); + verify(loginActivity, never()).addProgressIndicator(); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, ""); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + @Test public void onTokenReceived_shouldReturnAccessTokenResult() { String tokenString = "accessToken1234"; @@ -300,6 +377,19 @@ public void onCodeReceived_shouldReturnResultIntentWithCode() { assertThat(shadowActivity.isFinishing()).isTrue(); } + @Test + public void addProgressIndicator_shouldAddProgressIndicator() { + loginActivity.addProgressIndicator(); + assertThat(loginActivity.progressBarLayoutContainer).isNotNull(); + } + + @Test + public void removeProgressIndicator_shouldRemoveProgressIndicator() { + loginActivity.addProgressIndicator(); + loginActivity.removeProgressIndicator(); + assertThat(loginActivity.progressBarLayoutContainer).isNull(); + } + private AuthenticationError getErrorFromIntent(Intent intent) { return AuthenticationError.fromString(intent.getStringExtra(LoginManager.EXTRA_ERROR)); } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java index 94b1d76f..37b0f0af 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginManagerTest.java @@ -511,6 +511,26 @@ public void getSession_withoutAccessTokenOrToken_fails() { loginManager.getSession(); } + @Test + public void loginForAuthorizationCode_shouldEnablePARFlow() { + loginManager.loginForAuthorizationCode(activity); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), anyInt()); + Intent value = intentCaptor.getValue(); + assertThat((ResponseType)value.getSerializableExtra(EXTRA_RESPONSE_TYPE)).isEqualTo(ResponseType.CODE); + } + + @Test + public void loginForImplicitGrantWithFallback_shouldEnablePARFlow() { + loginManager.loginForImplicitGrantWithFallback(activity); + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + + verify(activity).startActivityForResult(intentCaptor.capture(), anyInt()); + Intent value = intentCaptor.getValue(); + assertThat((ResponseType)value.getSerializableExtra(EXTRA_RESPONSE_TYPE)).isEqualTo(ResponseType.TOKEN); + } + private void validateLoginIntentFields( @NonNull Intent loginIntent, @NonNull List expectedProductPriority, From 547e1ac943dfe13f5242c4cd35faf04ee6bb2741 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 9 May 2023 22:56:09 -0700 Subject: [PATCH 134/165] removed id for progress spineer --- .../main/java/com/uber/sdk/android/core/auth/LoginActivity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 839938b1..2205b287 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -427,7 +427,6 @@ private boolean isParFlow(Intent intent) { @VisibleForTesting void addProgressIndicator() { progressBarLayoutContainer = new LinearLayout(this); - progressBarLayoutContainer.setId(R.id.progress_spinner); progressBarLayoutContainer.setGravity(Gravity.CENTER); progressBarLayoutContainer.setLayoutParams( From f7f76f373020fe4166d31d05e0c1fbcf24e6bb63 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 9 May 2023 23:06:33 -0700 Subject: [PATCH 135/165] added shadow classes used in unit tests --- .../core/auth/ShadowLoginPARDispatcher.java | 17 +++++++++++++++++ .../auth/ShadowLoginPARDispatcherWithError.java | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java create mode 100644 core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java new file mode 100644 index 00000000..787f36b6 --- /dev/null +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java @@ -0,0 +1,17 @@ +package com.uber.sdk.android.core.auth; + +import com.uber.sdk.core.client.SessionConfiguration; +import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(LoginPARDispatcher.class) +public class ShadowLoginPARDispatcher { + @Implementation + public static void dispatchPAR(SessionConfiguration sessionConfiguration, + ResponseType responseType, + LoginActivity.LoginPARCallback callback) { + callback.onSuccess("requestUri"); + } +} \ No newline at end of file diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java new file mode 100644 index 00000000..afbae944 --- /dev/null +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java @@ -0,0 +1,17 @@ +package com.uber.sdk.android.core.auth; + +import com.uber.sdk.core.client.SessionConfiguration; +import com.uber.sdk.core.client.internal.LoginPARRequestException; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(LoginPARDispatcher.class) +public class ShadowLoginPARDispatcherWithError { + @Implementation + public static void dispatchPAR(SessionConfiguration sessionConfiguration, + ResponseType responseType, + LoginActivity.LoginPARCallback callback) { + callback.onError(null); + } +} \ No newline at end of file From 2a20934d0bbe899d352c02337656c5ed30e79545 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Fri, 19 May 2023 00:45:16 -0700 Subject: [PATCH 136/165] created new methods for newIntent to maintain backward compatibility --- .../sdk/android/core/auth/LoginActivity.java | 113 ++++++++++++------ .../sdk/android/core/auth/LoginManager.java | 11 +- .../android/core/auth/LoginActivityTest.java | 75 +++++++++--- 3 files changed, 142 insertions(+), 57 deletions(-) diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index 2205b287..eb87e99c 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -33,6 +33,7 @@ import androidx.annotation.VisibleForTesting; import androidx.browser.customtabs.CustomTabsIntent; import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.webkit.WebResourceError; @@ -48,6 +49,7 @@ import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.install.SignupDeeplink; import com.uber.sdk.android.core.utils.CustomTabsHelper; +import com.uber.sdk.core.auth.ProfileHint; import com.uber.sdk.core.client.SessionConfiguration; import com.uber.sdk.core.client.internal.LoginPARRequestException; import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; @@ -70,8 +72,6 @@ public class LoginActivity extends Activity { static final String EXTRA_REQUEST_URI = "REQUEST_URI"; - static final String EXTRA_PAR_FLOW = "PAR_FLOW"; - static final String ERROR = "error"; private ArrayList productPriority; @@ -112,27 +112,58 @@ public static Intent newIntent( /** * Create an {@link Intent} to pass to this activity * - * @param context the {@link Context} for the intent + * @param context the {@link Context} for the intent * @param sessionConfiguration to be used for gather clientId - * @param responseType that is expected - * @param isParFlow specifies whether to send user's profile info to Uber as part of login/signup flow + * @param responseType that is expected + * @param forceWebview Forced to use old webview instead of chrometabs * @return an intent that can be passed to this activity + * + * This method has been deprecated. Please use {@link LoginActivity#newIntent(Context, SessionConfiguration, ResponseType, String)} */ @NonNull + @Deprecated public static Intent newIntent( @NonNull Context context, @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType, - boolean isParFlow) { + boolean forceWebview) { - return newIntent(context, - new ArrayList(), - sessionConfiguration, - responseType, - "", - false, - false, - isParFlow); + return newIntent(context, new ArrayList(), sessionConfiguration, responseType, forceWebview, false, false); + } + + /** + * Create an {@link Intent} to pass to this activity + * + * @param context the {@link Context} for the intent + * @param productPriority dictates the order of which Uber applications should be used for SSO. + * @param sessionConfiguration to be used for gather clientId + * @param responseType that is expected + * @param forceWebview Forced to use old webview instead of chrometabs + * @param isSsoEnabled specifies whether to attempt login with SSO + * @return an intent that can be passed to this activity + * + * This method has been deprecated. Please use {@link LoginActivity#newIntent(Context, ArrayList, SessionConfiguration, ResponseType, String, boolean, boolean)} + */ + @NonNull + @Deprecated + static Intent newIntent( + @NonNull Context context, + @NonNull ArrayList productPriority, + @NonNull SessionConfiguration sessionConfiguration, + @NonNull ResponseType responseType, + boolean forceWebview, + boolean isSsoEnabled, + boolean isRedirectToPlayStoreEnabled) { + + final Intent data = new Intent(context, LoginActivity.class) + .putExtra(EXTRA_PRODUCT_PRIORITY, productPriority) + .putExtra(EXTRA_SESSION_CONFIGURATION, sessionConfiguration) + .putExtra(EXTRA_RESPONSE_TYPE, responseType) + .putExtra(EXTRA_FORCE_WEBVIEW, forceWebview) + .putExtra(EXTRA_SSO_ENABLED, isSsoEnabled) + .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled); + + return data; } /** @@ -157,7 +188,6 @@ public static Intent newIntent( responseType, requestUri, false, - false, false); } @@ -170,7 +200,6 @@ public static Intent newIntent( * @param responseType that is expected * @param isSsoEnabled specifies whether to attempt login with SSO * @param isRedirectToPlayStoreEnabled specifies whether to redirect to Play Store if Uber app is not installed - * @param isParFlow specifies whether to send user's profile info to Uber as part of login/signup flow * @return an intent that can be passed to this activity */ @NonNull @@ -181,8 +210,7 @@ static Intent newIntent( @NonNull ResponseType responseType, String requestUri, boolean isSsoEnabled, - boolean isRedirectToPlayStoreEnabled, - boolean isParFlow) { + boolean isRedirectToPlayStoreEnabled) { final Intent data = new Intent(context, LoginActivity.class) .putExtra(EXTRA_PRODUCT_PRIORITY, productPriority) @@ -190,8 +218,7 @@ static Intent newIntent( .putExtra(EXTRA_REQUEST_URI, requestUri) .putExtra(EXTRA_RESPONSE_TYPE, responseType) .putExtra(EXTRA_SSO_ENABLED, isSsoEnabled) - .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled) - .putExtra(EXTRA_PAR_FLOW, isParFlow); + .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled); return data; } @@ -239,24 +266,30 @@ protected void onNewIntent(Intent intent) { } protected void init() { + Log.d("yyyy", "intent is " + getIntent().getData()); if (getIntent().getData() != null) { handleResponse(getIntent().getData()); + } else if (isParFlow(getIntent())) { + handleParFlow(); } else { - sessionConfiguration = (SessionConfiguration) getIntent().getSerializableExtra(EXTRA_SESSION_CONFIGURATION); - responseType = (ResponseType) getIntent().getSerializableExtra(EXTRA_RESPONSE_TYPE); - if (isParFlow(getIntent())) { - addProgressIndicator(); - LoginPARDispatcher.dispatchPAR( - sessionConfiguration, - responseType, - new LoginPARCallback(responseType) - ); - } else { - loadUrl(); - } + loadUrl(); } } + /** + * Initiates Pushed Authorization Request + */ + @VisibleForTesting + void handleParFlow() { + responseType = (ResponseType) getIntent().getSerializableExtra(EXTRA_RESPONSE_TYPE); + addProgressIndicator(); + LoginPARDispatcher.dispatchPAR( + sessionConfiguration, + responseType, + new LoginPARCallback(responseType) + ); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -265,7 +298,8 @@ protected void onDestroy() { protected void loadUrl() { Intent intent = getIntent(); - + sessionConfiguration = (SessionConfiguration) getIntent().getSerializableExtra(EXTRA_SESSION_CONFIGURATION); + responseType = (ResponseType) getIntent().getSerializableExtra(EXTRA_RESPONSE_TYPE); productPriority = (ArrayList) intent.getSerializableExtra(EXTRA_PRODUCT_PRIORITY); if (!validateRequestParams()) { @@ -418,7 +452,18 @@ private void redirectToInstallApp(@NonNull Activity activity) { } private boolean isParFlow(Intent intent) { - return intent.getBooleanExtra(EXTRA_PAR_FLOW, false); + sessionConfiguration = (SessionConfiguration) getIntent().getSerializableExtra(EXTRA_SESSION_CONFIGURATION); + if (sessionConfiguration == null) { + return false; + } + ProfileHint profileHint = sessionConfiguration.getProfileHint(); + return (profileHint != null && + !(TextUtils.isEmpty(profileHint.getEmail()) && + TextUtils.isEmpty(profileHint.getFirstName()) && + TextUtils.isEmpty(profileHint.getLastName()) && + TextUtils.isEmpty(profileHint.getEmail()) + ) + ); } /** diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index c14ac48b..779d5714 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -29,7 +29,6 @@ import androidx.annotation.VisibleForTesting; import android.util.Log; -import android.widget.LinearLayout; import com.uber.sdk.android.core.SupportedAppType; import com.uber.sdk.android.core.UberSdk; @@ -104,8 +103,6 @@ public class LoginManager { @Deprecated private boolean redirectForAuthorizationCode = false; - private LinearLayout progressBarLayoutContainer; - /** * @param accessTokenStorage to store access token. * @param loginCallback callback to be called when {@link LoginManager#onActivityResult(Activity, int, int, Intent)} @@ -187,10 +184,9 @@ public void login(final @NonNull Activity activity) { productFlowPriority, sessionConfiguration, ResponseType.TOKEN, - "", - true, + false, true, - false); + true); activity.startActivityForResult(intent, requestCode); } else if (ssoDeeplink.isSupported(SsoDeeplink.FlowVersion.DEFAULT)) { ssoDeeplink.execute(SsoDeeplink.FlowVersion.DEFAULT); @@ -253,8 +249,7 @@ private void launchOnboardingFlow(Activity activity, responseType, "", false, - isRedirectToPlayStoreEnabled, - true); + isRedirectToPlayStoreEnabled); activity.startActivityForResult(intent, requestCode); } diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index c3052c31..94863677 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.widget.LinearLayout; import androidx.browser.customtabs.CustomTabsIntent; import com.google.common.collect.ImmutableList; @@ -41,10 +40,8 @@ import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowActivity; @@ -155,7 +152,7 @@ public void onLoginLoad_withNullResponseType_shouldReturnErrorResultIntent() { @Test public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, - loginConfiguration, ResponseType.TOKEN, "", true, true, false); + loginConfiguration, ResponseType.TOKEN, true, true, false); ActivityController controller = Robolectric.buildActivity(LoginActivity.class, intent); loginActivity = controller.get(); @@ -171,7 +168,7 @@ public void onLoginLoad_withSsoEnabled_andSupported_shouldExecuteSsoDeeplink() { @Test public void onLoginLoad_withSsoEnabled_andNotSupported_shouldReturnErrorResultIntent() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), productPriority, - loginConfiguration, ResponseType.TOKEN, "", true, true, false); + loginConfiguration, ResponseType.TOKEN, true, true, false); ActivityController controller = Robolectric.buildActivity(LoginActivity.class).newIntent(intent); loginActivity = controller.get(); @@ -246,7 +243,7 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP .setScopes(MIXED_SCOPES) .build(); Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), - new ArrayList(), loginConfiguration, ResponseType.TOKEN, "", false, true, false); + new ArrayList(), loginConfiguration, ResponseType.TOKEN, true, false, true); ShadowActivity shadowActivity = shadowOf(Robolectric.buildActivity(LoginActivity.class, intent).create().get()); @@ -256,7 +253,7 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP @Test @Config(shadows = ShadowLoginPARDispatcher.class ) - public void onLoginLoad_whenPARFlowEnabled_shouldAddProgressIndicator_andLoadCustomTab() { + public void onLoginLoad_whenProfileHintProvided_shouldAddProgressIndicator_andLoadCustomTab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration .newBuilder() @@ -279,7 +276,7 @@ public void onLoginLoad_whenPARFlowEnabled_shouldAddProgressIndicator_andLoadCus @Test @Config(shadows = ShadowLoginPARDispatcherWithError.class ) - public void onLoginLoad_whenPARFlowEnabled_andErrorResponse_shouldAddProgressIndicator_andLoadCustomTab() { + public void onLoginLoad_whenProfileHintProvided_andErrorResponse_shouldAddProgressIndicator_andLoadCustomTab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.CODE, true); @@ -294,26 +291,35 @@ public void onLoginLoad_whenPARFlowEnabled_andErrorResponse_shouldAddProgressInd } @Test - public void onLoginLoad_whenPARFlowEnabled_andProfileHintEmpty_shouldAddProgressIndicator_andLoadCustomTab() { + public void onLoginLoad_whenProfileHintIsNull_shouldNotAddProgressIndicator_andLoadCustomTab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.CODE, true); + ResponseType.CODE); ActivityController controller = Robolectric .buildActivity(LoginActivity.class, intent); loginActivity = spy(controller.get()); loginActivity.customTabsHelper = customTabsHelper; loginActivity.onCreate(new Bundle()); - verify(loginActivity).addProgressIndicator(); - verify(loginActivity).removeProgressIndicator(); + verify(loginActivity, never()).addProgressIndicator(); + verify(loginActivity, never()).removeProgressIndicator(); String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, ""); verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); } @Test - public void onLoginLoad_whenPARFlowDisabled_shouldNotAddProgressIndicator_andLoadCustomTab() { - Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, - ResponseType.CODE, false); + public void onLoginLoad_whenProfileHintHasEmptyFields_shouldNotAddProgressIndicator_andLoadCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), + loginConfiguration.newBuilder() + .setProfileHint( + new ProfileHint.Builder() + .firstName("") + .lastName("") + .email("") + .phone("") + .build() + ).build(), + ResponseType.CODE); ActivityController controller = Robolectric .buildActivity(LoginActivity.class, intent); @@ -326,6 +332,45 @@ public void onLoginLoad_whenPARFlowDisabled_shouldNotAddProgressIndicator_andLoa eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); } + @Test + @Config(shadows = ShadowLoginPARDispatcher.class) + public void handleParFlow_whenProfileHintIsValid_thenAddProgressIndicator_andLaunchCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), + loginConfiguration + .newBuilder() + .setProfileHint(new ProfileHint + .Builder() + .firstName("test") + .build()) + .build(), + ResponseType.CODE, true); + + ActivityController controller = Robolectric + .buildActivity(LoginActivity.class, intent); + LoginActivity loginActivity = spy(controller.get()); + loginActivity.customTabsHelper = customTabsHelper; + loginActivity.onCreate(new Bundle()); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, "requestUri"); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + + @Test + public void handleParFlow_whenProfileHintIsNull_thenAddProgressIndicator_andLaunchCustomTab() { + Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), + loginConfiguration, + ResponseType.CODE); + + ActivityController controller = Robolectric + .buildActivity(LoginActivity.class, intent); + LoginActivity loginActivity = spy(controller.get()); + loginActivity.customTabsHelper = customTabsHelper; + loginActivity.onCreate(new Bundle()); + String expectedUrl = AuthUtils.buildUrl(REDIRECT_URI, ResponseType.CODE, loginConfiguration, ""); + verify(customTabsHelper).openCustomTab(any(LoginActivity.class), any(CustomTabsIntent.class), + eq(Uri.parse(expectedUrl)), any(CustomTabsHelper.BrowserFallback.class)); + } + @Test public void onTokenReceived_shouldReturnAccessTokenResult() { String tokenString = "accessToken1234"; From 50eab00ec5438df9a0bbec7e50447427e6cb31f2 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Thu, 25 May 2023 12:47:31 -0700 Subject: [PATCH 137/165] addressed comments --- core-android/build.gradle | 1 - .../uber/sdk/android/core/auth/AuthUtils.java | 2 +- .../sdk/android/core/auth/LoginActivity.java | 53 ++++++------------- .../sdk/android/core/auth/LoginManager.java | 1 - .../android/core/auth/LoginPARDispatcher.java | 27 ---------- .../core/auth/ShadowLoginPARDispatcher.java | 16 ++++-- .../ShadowLoginPARDispatcherWithError.java | 16 ++++-- gradle/dependencies.gradle | 12 ++--- 8 files changed, 43 insertions(+), 85 deletions(-) delete mode 100644 core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java diff --git a/core-android/build.gradle b/core-android/build.gradle index 3a7bc3c5..fe0d2556 100644 --- a/core-android/build.gradle +++ b/core-android/build.gradle @@ -60,7 +60,6 @@ dependencies { implementation deps.support.appCompat implementation deps.support.annotations implementation deps.support.chrometabs - implementation deps.network.moshi testImplementation deps.test.junit testImplementation deps.test.assertj diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java index 19ac1787..108b5aae 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/AuthUtils.java @@ -259,7 +259,7 @@ static String buildUrl( @NonNull String redirectUri, @NonNull ResponseType responseType, @NonNull SessionConfiguration configuration, - @NonNull String requestUri) { + String requestUri) { final String CLIENT_ID_PARAM = "client_id"; final String ENDPOINT = "auth"; diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java index eb87e99c..e14843d2 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginActivity.java @@ -29,11 +29,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.browser.customtabs.CustomTabsIntent; import android.text.TextUtils; -import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.webkit.WebResourceError; @@ -44,6 +40,10 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.browser.customtabs.CustomTabsIntent; + import com.uber.sdk.android.core.BuildConfig; import com.uber.sdk.android.core.R; import com.uber.sdk.android.core.SupportedAppType; @@ -106,7 +106,12 @@ public static Intent newIntent( @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType) { - return newIntent(context, sessionConfiguration, responseType, ""); + return newIntent(context, + new ArrayList(), + sessionConfiguration, + responseType, + false, + false); } /** @@ -118,7 +123,7 @@ public static Intent newIntent( * @param forceWebview Forced to use old webview instead of chrometabs * @return an intent that can be passed to this activity * - * This method has been deprecated. Please use {@link LoginActivity#newIntent(Context, SessionConfiguration, ResponseType, String)} + * This method has been deprecated. Please use {@link LoginActivity#newIntent(Context, SessionConfiguration, ResponseType)} */ @NonNull @Deprecated @@ -142,7 +147,7 @@ public static Intent newIntent( * @param isSsoEnabled specifies whether to attempt login with SSO * @return an intent that can be passed to this activity * - * This method has been deprecated. Please use {@link LoginActivity#newIntent(Context, ArrayList, SessionConfiguration, ResponseType, String, boolean, boolean)} + * This method has been deprecated. Please use {@link LoginActivity#newIntent(Context, ArrayList, SessionConfiguration, ResponseType, boolean, boolean)} */ @NonNull @Deprecated @@ -166,31 +171,6 @@ static Intent newIntent( return data; } - /** - * Create an {@link Intent} to pass to this activity - * - * @param context the {@link Context} for the intent - * @param sessionConfiguration to be used for gather clientId - * @param responseType that is expected - * @param requestUri to be used to prefill user's profile hint information - * @return an intent that can be passed to this activity - */ - @NonNull - public static Intent newIntent( - @NonNull Context context, - @NonNull SessionConfiguration sessionConfiguration, - @NonNull ResponseType responseType, - @NonNull String requestUri) { - - return newIntent(context, - new ArrayList(), - sessionConfiguration, - responseType, - requestUri, - false, - false); - } - /** * Create an {@link Intent} to pass to this activity * @@ -208,14 +188,12 @@ static Intent newIntent( @NonNull ArrayList productPriority, @NonNull SessionConfiguration sessionConfiguration, @NonNull ResponseType responseType, - String requestUri, boolean isSsoEnabled, boolean isRedirectToPlayStoreEnabled) { final Intent data = new Intent(context, LoginActivity.class) .putExtra(EXTRA_PRODUCT_PRIORITY, productPriority) .putExtra(EXTRA_SESSION_CONFIGURATION, sessionConfiguration) - .putExtra(EXTRA_REQUEST_URI, requestUri) .putExtra(EXTRA_RESPONSE_TYPE, responseType) .putExtra(EXTRA_SSO_ENABLED, isSsoEnabled) .putExtra(EXTRA_REDIRECT_TO_PLAY_STORE_ENABLED, isRedirectToPlayStoreEnabled); @@ -266,7 +244,6 @@ protected void onNewIntent(Intent intent) { } protected void init() { - Log.d("yyyy", "intent is " + getIntent().getData()); if (getIntent().getData() != null) { handleResponse(getIntent().getData()); } else if (isParFlow(getIntent())) { @@ -283,11 +260,11 @@ protected void init() { void handleParFlow() { responseType = (ResponseType) getIntent().getSerializableExtra(EXTRA_RESPONSE_TYPE); addProgressIndicator(); - LoginPARDispatcher.dispatchPAR( + new LoginPushedAuthorizationRequest( sessionConfiguration, - responseType, + responseType.name(), new LoginPARCallback(responseType) - ); + ).execute(); } @Override diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java index 779d5714..7b15b748 100644 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java +++ b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginManager.java @@ -247,7 +247,6 @@ private void launchOnboardingFlow(Activity activity, productFlowPriority, sessionConfiguration, responseType, - "", false, isRedirectToPlayStoreEnabled); activity.startActivityForResult(intent, requestCode); diff --git a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java b/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java deleted file mode 100644 index 0f71e806..00000000 --- a/core-android/src/main/java/com/uber/sdk/android/core/auth/LoginPARDispatcher.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.uber.sdk.android.core.auth; - -import com.uber.sdk.core.auth.internal.OAuth2Service; -import com.uber.sdk.core.client.SessionConfiguration; -import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; - -import retrofit2.Retrofit; -import retrofit2.converter.moshi.MoshiConverterFactory; - -public class LoginPARDispatcher { - - public static void dispatchPAR(SessionConfiguration sessionConfiguration, - ResponseType responseType, - LoginActivity.LoginPARCallback callback) { - new LoginPushedAuthorizationRequest( - new Retrofit.Builder() - .baseUrl(sessionConfiguration.getLoginHost()) - .addConverterFactory(MoshiConverterFactory.create()) - .build() - .create(OAuth2Service.class), - sessionConfiguration.getProfileHint(), - sessionConfiguration.getClientId(), - responseType.name(), - callback - ).execute(); - } -} diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java index 787f36b6..26427dbe 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java @@ -6,12 +6,20 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(LoginPARDispatcher.class) +@Implements(LoginPushedAuthorizationRequest.class) public class ShadowLoginPARDispatcher { + + private LoginPushedAuthorizationRequest.Callback callback; + + @Implementation + public void __constructor__ (SessionConfiguration sessionConfiguration, + String responseType, + LoginPushedAuthorizationRequest.Callback callback) { + this.callback = callback; + } + @Implementation - public static void dispatchPAR(SessionConfiguration sessionConfiguration, - ResponseType responseType, - LoginActivity.LoginPARCallback callback) { + public void execute() { callback.onSuccess("requestUri"); } } \ No newline at end of file diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java index afbae944..abe13b67 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java @@ -2,16 +2,24 @@ import com.uber.sdk.core.client.SessionConfiguration; import com.uber.sdk.core.client.internal.LoginPARRequestException; +import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(LoginPARDispatcher.class) +@Implements(LoginPushedAuthorizationRequest.class) public class ShadowLoginPARDispatcherWithError { + private LoginPushedAuthorizationRequest.Callback callback; + + @Implementation + public void __constructor__ (SessionConfiguration sessionConfiguration, + String responseType, + LoginPushedAuthorizationRequest.Callback callback) { + this.callback = callback; + } + @Implementation - public static void dispatchPAR(SessionConfiguration sessionConfiguration, - ResponseType responseType, - LoginActivity.LoginPARCallback callback) { + public void execute() { callback.onError(null); } } \ No newline at end of file diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index e73eb45d..da957467 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -20,16 +20,12 @@ def versions = [ uberJava: '0.8.1', ] -def network = [ - moshi: 'com.squareup.moshi:moshi:1.9.2', -] - def build = [ gradleVersion: '4.9', buildToolsVersion: '28.0.2', compileSdkVersion: 28, ci: 'true' == System.getenv('CI'), - minSdkVersion: 16, + minSdkVersion: 15, targetSdkVersion: 28, repositories: [ @@ -40,8 +36,7 @@ def build = [ android: 'com.android.tools.build:gradle:3.3.2', release: 'net.researchgate:gradle-release:2.1.2', github: 'co.riiid:gradle-github-plugin:0.4.2', - cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1', - buildConfig: 'gradle.plugin.de.fuerstenau:BuildConfigPlugin:1.1.8' + cobertura: 'net.saliman:gradle-cobertura-plugin:2.3.1' ] ] @@ -77,6 +72,5 @@ ext.deps = [ "support": support, "test": test, "versions": versions, - "uber": uber, - "network": network + "uber": uber ] From c5720e5a4a98a16857d4cd3ade391455fda92fe3 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Thu, 25 May 2023 13:30:30 -0700 Subject: [PATCH 138/165] renamed ShadowLoginPARDispatch to ShadowLoginPushedAUthorizationRequest --- .../com/uber/sdk/android/core/auth/LoginActivityTest.java | 6 +++--- ...cher.java => ShadowLoginPushedAuthorizationRequest.java} | 2 +- ... => ShadowLoginPushedAuthorizationRequestWithError.java} | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) rename core-android/src/test/java/com/uber/sdk/android/core/auth/{ShadowLoginPARDispatcher.java => ShadowLoginPushedAuthorizationRequest.java} (93%) rename core-android/src/test/java/com/uber/sdk/android/core/auth/{ShadowLoginPARDispatcherWithError.java => ShadowLoginPushedAuthorizationRequestWithError.java} (86%) diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java index 94863677..335e17cf 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/LoginActivityTest.java @@ -252,7 +252,7 @@ public void onLoginLoad_withResponseTypeToken_andPrivilegedScopes_andRedirectToP } @Test - @Config(shadows = ShadowLoginPARDispatcher.class ) + @Config(shadows = ShadowLoginPushedAuthorizationRequest.class ) public void onLoginLoad_whenProfileHintProvided_shouldAddProgressIndicator_andLoadCustomTab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration @@ -275,7 +275,7 @@ public void onLoginLoad_whenProfileHintProvided_shouldAddProgressIndicator_andLo } @Test - @Config(shadows = ShadowLoginPARDispatcherWithError.class ) + @Config(shadows = ShadowLoginPushedAuthorizationRequestWithError.class ) public void onLoginLoad_whenProfileHintProvided_andErrorResponse_shouldAddProgressIndicator_andLoadCustomTab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration, ResponseType.CODE, true); @@ -333,7 +333,7 @@ public void onLoginLoad_whenProfileHintHasEmptyFields_shouldNotAddProgressIndica } @Test - @Config(shadows = ShadowLoginPARDispatcher.class) + @Config(shadows = ShadowLoginPushedAuthorizationRequest.class) public void handleParFlow_whenProfileHintIsValid_thenAddProgressIndicator_andLaunchCustomTab() { Intent intent = LoginActivity.newIntent(Robolectric.setupActivity(Activity.class), loginConfiguration diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPushedAuthorizationRequest.java similarity index 93% rename from core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java rename to core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPushedAuthorizationRequest.java index 26427dbe..280180c3 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcher.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPushedAuthorizationRequest.java @@ -7,7 +7,7 @@ import org.robolectric.annotation.Implements; @Implements(LoginPushedAuthorizationRequest.class) -public class ShadowLoginPARDispatcher { +public class ShadowLoginPushedAuthorizationRequest { private LoginPushedAuthorizationRequest.Callback callback; diff --git a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPushedAuthorizationRequestWithError.java similarity index 86% rename from core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java rename to core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPushedAuthorizationRequestWithError.java index abe13b67..a247b409 100644 --- a/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPARDispatcherWithError.java +++ b/core-android/src/test/java/com/uber/sdk/android/core/auth/ShadowLoginPushedAuthorizationRequestWithError.java @@ -1,14 +1,13 @@ package com.uber.sdk.android.core.auth; import com.uber.sdk.core.client.SessionConfiguration; -import com.uber.sdk.core.client.internal.LoginPARRequestException; import com.uber.sdk.core.client.internal.LoginPushedAuthorizationRequest; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @Implements(LoginPushedAuthorizationRequest.class) -public class ShadowLoginPARDispatcherWithError { +public class ShadowLoginPushedAuthorizationRequestWithError { private LoginPushedAuthorizationRequest.Callback callback; @Implementation From 8447c674efdec83dea9332f46c43733c68e5b7f2 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Thu, 25 May 2023 16:10:09 -0700 Subject: [PATCH 139/165] upgrading minSdk version to 26 --- gradle/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index da957467..9c3f9292 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -25,7 +25,7 @@ def build = [ buildToolsVersion: '28.0.2', compileSdkVersion: 28, ci: 'true' == System.getenv('CI'), - minSdkVersion: 15, + minSdkVersion: 26, targetSdkVersion: 28, repositories: [ From 9d9fd9d87eff590acf0e73adc45cc8653441805c Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Thu, 25 May 2023 16:21:13 -0700 Subject: [PATCH 140/165] Update README.md added documentation for using profile prefill feature --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index e4eb2d81..295ecb92 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,24 @@ user1Manager.setAccessToken(accessToken); user2Manager.setAccessToken(accessToken2); ``` +### Prefilling User Information +If you would like text fields during signup to be pre-populated with user information you can do so using the ProfileHint API. Partial information is accepted. You will need to supply a ProfileHint object when creating SessionConfiguration instance. + +``` +SessionConfiguration configuration = new SessionConfiguration.Builder() + .setClientId(CLIENT_ID) + .setRedirectUri(REDIRECT_URI) + .setScopes(Arrays.asList(Scope.PROFILE, Scope.RIDE_WIDGETS)) + .setProfileHint(new ProfileHint + .Builder() + .email("john@doe.com") + .firstName("John") + .lastName("Doe") + .phone("1234567890") + .build()) + .build(); +``` + ## Making an API Request The Android Uber SDK uses a dependency on the Java Uber SDK for API requests. After authentication is complete, create a `Session` to use the Uber API. From c5b73eabb39f94cd175422a53deaa1367e2a6e76 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Fri, 26 May 2023 10:53:15 -0700 Subject: [PATCH 141/165] Update gradle.properties preparing for the next release --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index d7ce2d91..5de72bc0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.3 -VERSION_NAME=0.10.3 +version=0.10.4 +VERSION_NAME=0.10.4 POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 66d1efd1f51d552cdc62cb16f4746b3dee129edb Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Fri, 26 May 2023 12:12:23 -0700 Subject: [PATCH 142/165] updated changelog file --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 185849f8..c87f242d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +v0.10.4 - 05/26/2023 +------------- + +### Added + - [Issue #194](https://github.com/uber/rides-android-sdk/issues/194) Integrated Login Pushed authorization request flow + - [Issue #193](https://github.com/uber/rides-android-sdk/issues/193) Deprecating embedded webviews + - Updated java version to 1.8 + - Updated login endpoint to auth.uber.com from login.uber.com + - Replaced jcenter with mavenCentral + v0.10.3 - 08/19/2021 ------------- From 590b69f76fc61193b0b8f72c79a660cb3a335047 Mon Sep 17 00:00:00 2001 From: Saurabh Lalwani Date: Tue, 30 May 2023 12:47:57 -0700 Subject: [PATCH 143/165] bumping up the version to prepare for next release --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5de72bc0..a768ed2d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ GROUP=com.uber.sdk #Version is managed by Gradle Release Plugin -version=0.10.4 -VERSION_NAME=0.10.4 +version=0.10.5-SNAPSHOT +VERSION_NAME=0.10.5-SNAPSHOT POM_URL=https\://developer.uber.com POM_SCM_URL=https\://github.com/uber/rides-android-sdk/ From 7748df8c10ffcb0457fc03897d827b1fff0d4b29 Mon Sep 17 00:00:00 2001 From: lalwani Date: Mon, 7 Aug 2023 14:32:52 -0700 Subject: [PATCH 144/165] created login demo using applink --- samples/login-with-auth-code-demo/.gitignore | 3 + .../login-with-auth-code-demo/build.gradle | 70 ++++++++ .../gradle.properties | 3 + samples/login-with-auth-code-demo/lint.xml | 30 ++++ .../src/main/AndroidManifest.xml | 46 +++++ .../sdk/android/samples/DemoActivity.java | 169 ++++++++++++++++++ .../samples/auth/AuthUriAssembler.java | 63 +++++++ .../sdk/android/samples/auth/PkceUtil.java | 47 +++++ .../android/samples/model/AccessToken.java | 89 +++++++++ .../android/samples/network/AuthService.java | 40 +++++ .../network/AuthorizationCodeGrantFlow.java | 102 +++++++++++ .../network/TokenRequestFlowCallback.java | 41 +++++ .../drawable-hdpi/uber_sample_ic_launcher.png | Bin 0 -> 3333 bytes .../drawable-mdpi/uber_sample_ic_launcher.png | Bin 0 -> 2061 bytes .../uber_sample_ic_launcher.png | Bin 0 -> 4771 bytes .../uber_sample_ic_launcher.png | Bin 0 -> 8075 bytes .../uber_sample_ic_launcher.png | Bin 0 -> 10580 bytes .../src/main/res/layout/activity_sample.xml | 47 +++++ .../src/main/res/menu/menu_main.xml | 36 ++++ .../src/main/res/values/dimens.xml | 26 +++ .../src/main/res/values/strings.xml | 38 ++++ settings.gradle | 1 + 22 files changed, 851 insertions(+) create mode 100644 samples/login-with-auth-code-demo/.gitignore create mode 100644 samples/login-with-auth-code-demo/build.gradle create mode 100644 samples/login-with-auth-code-demo/gradle.properties create mode 100644 samples/login-with-auth-code-demo/lint.xml create mode 100644 samples/login-with-auth-code-demo/src/main/AndroidManifest.xml create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/DemoActivity.java create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/AuthUriAssembler.java create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/PkceUtil.java create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/model/AccessToken.java create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthService.java create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthorizationCodeGrantFlow.java create mode 100644 samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/TokenRequestFlowCallback.java create mode 100755 samples/login-with-auth-code-demo/src/main/res/drawable-hdpi/uber_sample_ic_launcher.png create mode 100755 samples/login-with-auth-code-demo/src/main/res/drawable-mdpi/uber_sample_ic_launcher.png create mode 100755 samples/login-with-auth-code-demo/src/main/res/drawable-xhdpi/uber_sample_ic_launcher.png create mode 100644 samples/login-with-auth-code-demo/src/main/res/drawable-xxhdpi/uber_sample_ic_launcher.png create mode 100755 samples/login-with-auth-code-demo/src/main/res/drawable-xxxhdpi/uber_sample_ic_launcher.png create mode 100644 samples/login-with-auth-code-demo/src/main/res/layout/activity_sample.xml create mode 100644 samples/login-with-auth-code-demo/src/main/res/menu/menu_main.xml create mode 100644 samples/login-with-auth-code-demo/src/main/res/values/dimens.xml create mode 100644 samples/login-with-auth-code-demo/src/main/res/values/strings.xml diff --git a/samples/login-with-auth-code-demo/.gitignore b/samples/login-with-auth-code-demo/.gitignore new file mode 100644 index 00000000..2bdf0f6a --- /dev/null +++ b/samples/login-with-auth-code-demo/.gitignore @@ -0,0 +1,3 @@ +/build +/debug +/release \ No newline at end of file diff --git a/samples/login-with-auth-code-demo/build.gradle b/samples/login-with-auth-code-demo/build.gradle new file mode 100644 index 00000000..ad04453a --- /dev/null +++ b/samples/login-with-auth-code-demo/build.gradle @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.2' + } +} + +apply plugin: 'com.android.application' +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "com.uber.sdk.android.samples" + minSdkVersion 26 + //noinspection ExpiredTargetSdkVersion + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + buildConfigField "String", "CLIENT_ID", "\"${loadSecret("UBER_CLIENT_ID")}\"" //Add your client id to gradle.properties + buildConfigField "String", "REDIRECT_URI", "\"${loadSecret("UBER_REDIRECT_URI")}\"" //Add your redirect uri to gradle.properties + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' + implementation 'com.squareup.moshi:moshi:1.2.0' +} + +/** + * Loads property from gradle.properties and ~/.gradle/gradle.properties + * Use to look up confidential information like keys that shoudln't be stored publicly + * @param name to lookup + * @return the value of the property or "MISSING" + */ +def loadSecret(String name) { + return hasProperty(name) ? getProperty(name) : "MISSING" +} diff --git a/samples/login-with-auth-code-demo/gradle.properties b/samples/login-with-auth-code-demo/gradle.properties new file mode 100644 index 00000000..a1c9d5e1 --- /dev/null +++ b/samples/login-with-auth-code-demo/gradle.properties @@ -0,0 +1,3 @@ +description=Login to Uber Sample +UBER_CLIENT_ID=insert_your_client_id_here +UBER_REDIRECT_URI=insert_your_redirect_uri_here \ No newline at end of file diff --git a/samples/login-with-auth-code-demo/lint.xml b/samples/login-with-auth-code-demo/lint.xml new file mode 100644 index 00000000..96c865ba --- /dev/null +++ b/samples/login-with-auth-code-demo/lint.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/samples/login-with-auth-code-demo/src/main/AndroidManifest.xml b/samples/login-with-auth-code-demo/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3e477291 --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/DemoActivity.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/DemoActivity.java new file mode 100644 index 00000000..c2b65ca1 --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/DemoActivity.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023 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.samples; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Button; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.uber.sdk.android.rides.samples.BuildConfig; +import com.uber.sdk.android.rides.samples.R; +import com.uber.sdk.android.samples.auth.AuthUriAssembler; +import com.uber.sdk.android.samples.auth.PkceUtil; +import com.uber.sdk.android.samples.model.AccessToken; +import com.uber.sdk.android.samples.network.AuthorizationCodeGrantFlow; +import com.uber.sdk.android.samples.network.TokenRequestFlowCallback; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; + + +public class DemoActivity extends AppCompatActivity { + private static final String LOG_TAG = DemoActivity.class.getSimpleName(); + public static final String CLIENT_ID = BuildConfig.CLIENT_ID; + public static final String REDIRECT_URI = BuildConfig.REDIRECT_URI; + + public static final String BASE_URL = "https://auth.uber.com"; + private static final String ACCESS_TOKEN_SHARED_PREFERENCES = ".demoActivityStorage"; + private static final String ACCESS_TOKEN = ".access_token"; + private static final String EXTRA_CODE_RECEIVED = "CODE_RECEIVED"; + private static final int CUSTOM_BUTTON_REQUEST_CODE = 1111; + private static final String CODE_VERIFIER = PkceUtil.generateCodeVerifier(); + + private SharedPreferences sharedPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sample); + sharedPreferences = getApplicationContext() + .getSharedPreferences(ACCESS_TOKEN_SHARED_PREFERENCES, Context.MODE_PRIVATE); + Button appLinkButton = findViewById(R.id.applink_uber_button); + appLinkButton.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_VIEW); + String codeChallenge; + try { + codeChallenge = PkceUtil.generateCodeChallange(CODE_VERIFIER); + } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + intent.setData( + AuthUriAssembler.assemble( + CLIENT_ID, + "profile", + "code", + codeChallenge, + REDIRECT_URI + ) + ); + startActivityForResult(intent, CUSTOM_BUTTON_REQUEST_CODE); + }); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.i(LOG_TAG, String.format("onActivityResult requestCode:[%s] resultCode [%s]", + requestCode, resultCode)); + if (data != null) { + final String authorizationCode = data.getStringExtra(EXTRA_CODE_RECEIVED); + if (authorizationCode != null) { + handleAuthCode(authorizationCode); + } + } + } + + private void handleAuthCode(String authCode) { + new AuthorizationCodeGrantFlow( + BASE_URL, + CLIENT_ID, + REDIRECT_URI, + authCode, + CODE_VERIFIER + ).execute(new TokenRequestFlowCallback() { + @Override + public void onSuccess(AccessToken accessToken) { + sharedPreferences.edit() + .putString(ACCESS_TOKEN, accessToken.getToken()) + .apply(); + } + + @Override + public void onFailure(Throwable throwable) { + Toast.makeText( + DemoActivity.this, + getString(R.string.authorization_code_error_message, throwable.getMessage()), + Toast.LENGTH_LONG + ).show(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + + if (id == R.id.action_clear) { + sharedPreferences.edit().clear().apply(); + Toast.makeText(this, "AccessToken cleared", Toast.LENGTH_SHORT).show(); + return true; + } else if (id == R.id.action_copy) { + String accessToken = sharedPreferences.getString(ACCESS_TOKEN, ""); + + String message = accessToken.isEmpty() ? "No AccessToken stored" : "AccessToken copied to clipboard"; + if (!accessToken.isEmpty()) { + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("UberDemoAccessToken", accessToken); + clipboard.setPrimaryClip(clip); + } + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/AuthUriAssembler.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/AuthUriAssembler.java new file mode 100644 index 00000000..9193ce9c --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/AuthUriAssembler.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 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.samples.auth; + +import android.net.Uri; + +import androidx.annotation.NonNull; + +import com.uber.sdk.android.rides.samples.BuildConfig; + +import java.util.Locale; + +public class AuthUriAssembler { + static final String CLIENT_ID_PARAM = "client_id"; + static final String HTTPS = "https"; + static final String PATH = "oauth/v2/universal/authorize"; + static final String REDIRECT_PARAM = "redirect_uri"; + static final String RESPONSE_TYPE_PARAM = "response_type"; + static final String SCOPE_PARAM = "scope"; + static final String PLATFORM_PARAM = "sdk"; + static final String SDK_VERSION_PARAM = "sdk_version"; + static final String CODE_CHALLENGE_PARAM = "code_challenge"; + public static Uri assemble( + @NonNull String clientId, + @NonNull String scopes, + @NonNull String responseType, + @NonNull String codeChallenge, + @NonNull String redirectUri) { + + Uri.Builder builder = new Uri.Builder(); + builder.scheme(HTTPS) + .authority("auth.uber.com") + .appendEncodedPath(PATH) + .appendQueryParameter(CLIENT_ID_PARAM, clientId) + .appendQueryParameter(RESPONSE_TYPE_PARAM, responseType.toLowerCase(Locale.US)) + .appendQueryParameter(PLATFORM_PARAM, "android") + .appendQueryParameter(REDIRECT_PARAM, redirectUri) + .appendQueryParameter(SDK_VERSION_PARAM, BuildConfig.VERSION_NAME) + .appendQueryParameter(SCOPE_PARAM, scopes) + .appendQueryParameter(CODE_CHALLENGE_PARAM, codeChallenge); + return builder.build(); + } +} diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/PkceUtil.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/PkceUtil.java new file mode 100644 index 00000000..e29d26f1 --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/auth/PkceUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 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.samples.auth; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +public class PkceUtil { + public static String generateCodeVerifier() { + SecureRandom secureRandom = new SecureRandom(); + byte[] codeVerifier = new byte[32]; + secureRandom.nextBytes(codeVerifier); + return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier); + } + + public static String generateCodeChallange(String codeVerifier) throws UnsupportedEncodingException, NoSuchAlgorithmException { + byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII); + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(bytes, 0, bytes.length); + byte[] digest = messageDigest.digest(); + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); + } +} diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/model/AccessToken.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/model/AccessToken.java new file mode 100644 index 00000000..20280c52 --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/model/AccessToken.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 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.samples.model; + +import java.util.Objects; + +public class AccessToken { + private final long expires_in; + private final String scopes; + private final String access_token; + private final String refresh_token; + private final String token_type; + + /** + * @param expiresIn the time that the access token expires. + * @param scopes space delimited list of Scopes. + * @param token the Uber API access token. + * @param refreshToken the Uber API refresh token. + * @param tokenType the Uber API token type. + */ + public AccessToken( + long expiresIn, + String scopes, + String token, + String refreshToken, + String tokenType) { + this.expires_in = expiresIn; + this.scopes = scopes; + this.access_token = token; + this.refresh_token = refreshToken; + this.token_type = tokenType; + } + + /** + * Gets the raw token used to make API requests + * + * @return the raw token. + */ + public String getToken() { + return access_token; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AccessToken that = (AccessToken) o; + + if (expires_in != that.expires_in) return false; + if (!Objects.equals(scopes, that.scopes)) return false; + if (!Objects.equals(access_token, that.access_token)) + return false; + if (!Objects.equals(refresh_token, that.refresh_token)) + return false; + return Objects.equals(token_type, that.token_type); + + } + + @Override + public int hashCode() { + int result = (int) (expires_in ^ (expires_in >>> 32)); + result = 31 * result + (scopes != null ? scopes.hashCode() : 0); + result = 31 * result + (access_token != null ? access_token.hashCode() : 0); + result = 31 * result + (refresh_token != null ? refresh_token.hashCode() : 0); + result = 31 * result + (token_type != null ? token_type.hashCode() : 0); + return result; + } +} diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthService.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthService.java new file mode 100644 index 00000000..db372aae --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthService.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 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.samples.network; + +import com.uber.sdk.android.samples.model.AccessToken; + +import retrofit2.Call; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.POST; + +public interface AuthService { + @FormUrlEncoded + @POST("/oauth/v2/token") + Call token(@Field("client_id") String clientId, + @Field("code_verifier") String codeVerifier, + @Field("grant_type") String grantType, + @Field("redirect_uri") String redirectUri, + @Field("code") String authCode); +} diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthorizationCodeGrantFlow.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthorizationCodeGrantFlow.java new file mode 100644 index 00000000..c94fe571 --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/AuthorizationCodeGrantFlow.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 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.samples.network; + +import androidx.annotation.NonNull; + +import com.uber.sdk.android.samples.model.AccessToken; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.moshi.MoshiConverterFactory; + +public class AuthorizationCodeGrantFlow { + private final static String GRANT_TYPE = "authorization_code"; + + private final AuthService authService; + private final String clientId; + private final String redirectUri; + private final String authCode; + private final String codeVerifier; + + /** + * @param baseUrl domain/authority to send the oauth token request + * @param clientId oauth clientId of the app + * @param redirectUri redirectUri configured as part of the oauth flow + * @param authCode authCode that was delivered as part of redirectUri when user was authenticated + * @param codeVerifier code verifier that was generated as part of code challenge-verifier pair + */ + public AuthorizationCodeGrantFlow( + String baseUrl, + String clientId, + String redirectUri, + String authCode, + String codeVerifier + ) { + this.authService = createOAuthService(baseUrl); + this.clientId = clientId; + this.redirectUri = redirectUri; + this.authCode = authCode; + this.codeVerifier = codeVerifier; + } + + public void execute(TokenRequestFlowCallback callback) { + authService.token( + clientId, + codeVerifier, + GRANT_TYPE, + redirectUri, + authCode + ).enqueue( + new Callback() { + @Override + public void onResponse( + @NonNull Call call, + @NonNull Response response) { + if (response.isSuccessful()) { + callback.onSuccess(response.body()); + } else { + onFailure(call, new RuntimeException("Token request failed with code " + response.code())); + } + } + + @Override + public void onFailure( + @NonNull Call call, + @NonNull Throwable t) { + callback.onFailure(t); + } + } + ); + } + + private static AuthService createOAuthService(String baseUrl) { + return new Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(AuthService.class); + } +} diff --git a/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/TokenRequestFlowCallback.java b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/TokenRequestFlowCallback.java new file mode 100644 index 00000000..01b5c16d --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/java/com/uber/sdk/android/samples/network/TokenRequestFlowCallback.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 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.samples.network; + +import com.uber.sdk.android.samples.model.AccessToken; + +public interface TokenRequestFlowCallback { + /** + * Called when token request finishes successfully + * + * @param accessToken {@link AccessToken} object containing oauth tokens + */ + void onSuccess(AccessToken accessToken); + + /** + * Called when token request finishes with a failure + * + * @param error throwable containing reason for the failure + */ + void onFailure(Throwable error); +} \ No newline at end of file diff --git a/samples/login-with-auth-code-demo/src/main/res/drawable-hdpi/uber_sample_ic_launcher.png b/samples/login-with-auth-code-demo/src/main/res/drawable-hdpi/uber_sample_ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..26369aeb942afaf764e9141a5a2f253fc0b27c0e GIT binary patch literal 3333 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 literal 0 HcmV?d00001 diff --git a/samples/login-with-auth-code-demo/src/main/res/layout/activity_sample.xml b/samples/login-with-auth-code-demo/src/main/res/layout/activity_sample.xml new file mode 100644 index 00000000..0ad1ae47 --- /dev/null +++ b/samples/login-with-auth-code-demo/src/main/res/layout/activity_sample.xml @@ -0,0 +1,47 @@ + + + + + + +