Skip to content

Commit

Permalink
Merge 99b6ea3 into e684c22
Browse files Browse the repository at this point in the history
  • Loading branch information
najmsheikh committed Aug 14, 2018
2 parents e684c22 + 99b6ea3 commit e853f0a
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 47 deletions.
2 changes: 2 additions & 0 deletions button-merchant/build.gradle
Expand Up @@ -55,6 +55,7 @@ android {

testOptions {
unitTests.returnDefaultValues = true
unitTests.includeAndroidResources = true
}
}

Expand Down Expand Up @@ -106,6 +107,7 @@ dependencies {
testImplementation "junit:junit:$junitVersion"
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
testImplementation "org.robolectric:robolectric:3.8"
testImplementation 'org.json:json:20171018'
androidTestImplementation "com.android.support.test:runner:$testRunnerVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion"
Expand Down
Expand Up @@ -34,6 +34,7 @@
import com.usebutton.merchant.exception.ApplicationIdNotFoundException;

import java.util.ArrayList;
import java.util.concurrent.Executor;

/**
* ButtonInternalImpl class should implement everything needed for {@link ButtonMerchant}
Expand All @@ -52,8 +53,16 @@ final class ButtonInternalImpl implements ButtonInternal {
@VisibleForTesting
ArrayList<ButtonMerchant.AttributionTokenListener> attributionTokenListeners;

ButtonInternalImpl() {
/**
* An {@link Executor} that ensures callbacks are run on the main thread.
*
* @see MainThreadExecutor
*/
private final Executor executor;

ButtonInternalImpl(Executor executor) {
this.attributionTokenListeners = new ArrayList<>();
this.executor = executor;
}

public void configure(ButtonRepository buttonRepository, String applicationId) {
Expand Down Expand Up @@ -82,7 +91,12 @@ public void trackOrder(ButtonRepository buttonRepository, DeviceManager manager,
@NonNull Order order, @Nullable final UserActivityListener listener) {
if (buttonRepository.getApplicationId() == null) {
if (listener != null) {
listener.onResult(new ApplicationIdNotFoundException());
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(new ApplicationIdNotFoundException());
}
});
}

return;
Expand All @@ -93,14 +107,24 @@ public void trackOrder(ButtonRepository buttonRepository, DeviceManager manager,
@Override
public void onTaskComplete(@Nullable Object object) {
if (listener != null) {
listener.onResult(null);
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(null);
}
});
}
}

@Override
public void onTaskError(Throwable throwable) {
public void onTaskError(final Throwable throwable) {
if (listener != null) {
listener.onResult(throwable);
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(throwable);
}
});
}
}
};
Expand Down Expand Up @@ -137,12 +161,22 @@ public void handlePostInstallIntent(final ButtonRepository buttonRepository,
DeviceManager deviceManager) {

if (buttonRepository.getApplicationId() == null) {
listener.onResult(null, new ApplicationIdNotFoundException());
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(null, new ApplicationIdNotFoundException());
}
});
return;
}

if (deviceManager.isOldInstallation() || buttonRepository.checkedDeferredDeepLink()) {
listener.onResult(null, null);
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(null, null);
}
});
return;
}

Expand All @@ -153,7 +187,7 @@ public void onTaskComplete(@Nullable PostInstallLink postInstallLink) {
if (postInstallLink != null
&& postInstallLink.isMatch()
&& postInstallLink.getAction() != null) {
Intent deepLinkIntent =
final Intent deepLinkIntent =
new Intent(Intent.ACTION_VIEW, Uri.parse(postInstallLink.getAction()));
deepLinkIntent.setPackage(packageName);

Expand All @@ -162,16 +196,31 @@ public void onTaskComplete(@Nullable PostInstallLink postInstallLink) {
setAttributionToken(buttonRepository, attribution.getBtnRef());
}

listener.onResult(deepLinkIntent, null);
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(deepLinkIntent, null);
}
});
return;
}

listener.onResult(null, null);
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(null, null);
}
});
}

@Override
public void onTaskError(Throwable throwable) {
listener.onResult(null, throwable);
public void onTaskError(final Throwable throwable) {
executor.execute(new Runnable() {
@Override
public void run() {
listener.onResult(null, throwable);
}
});
}
}, deviceManager);
}
Expand All @@ -183,15 +232,22 @@ public void onTaskError(Throwable throwable) {
* @param buttonRepository {@link ButtonRepository}
* @param attributionToken The attributionToken.
*/
private void setAttributionToken(ButtonRepository buttonRepository, String attributionToken) {
private void setAttributionToken(ButtonRepository buttonRepository,
final String attributionToken) {
if (attributionToken != null && !attributionToken.isEmpty()) {

// check if the sourceToken has changed
if (!attributionToken.equals(getAttributionToken(buttonRepository))) {
// notify all listeners that the attributionToken has changed
for (ButtonMerchant.AttributionTokenListener listener : attributionTokenListeners) {
for (final ButtonMerchant.AttributionTokenListener listener
: attributionTokenListeners) {
if (listener != null) {
listener.onAttributionTokenChanged(attributionToken);
executor.execute(new Runnable() {
@Override
public void run() {
listener.onAttributionTokenChanged(attributionToken);
}
});
}
}
}
Expand Down
Expand Up @@ -31,6 +31,7 @@
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

Expand All @@ -43,8 +44,9 @@ private ButtonMerchant() {

}

private static Executor executor = new MainThreadExecutor();
@VisibleForTesting
static ButtonInternal buttonInternal = new ButtonInternalImpl();
static ButtonInternal buttonInternal = new ButtonInternalImpl(executor);

private static ExecutorService executorService = Executors.newSingleThreadExecutor();

Expand Down Expand Up @@ -87,13 +89,13 @@ public static void trackOrder(@NonNull Context context, @NonNull Order order,
*
* For attribution to work correctly, you must:
* <ul>
* <li>Always access this token directly—*never cache it*.</li>
* <li>Never manage the lifecycle of this token—Button manages the token validity window
* server-side.</li>
* <li>Always include this value when reporting orders to your order API.</li>
* <li>Always access this token directly—*never cache it*.</li>
* <li>Never manage the lifecycle of this token—Button manages the token validity window
* server-side.</li>
* <li>Always include this value when reporting orders to your order API.</li>
* </ul>
*
* @return the last tracked Button attribution token.
* @return the last tracked Button attribution token.
**/
@Nullable
public static String getAttributionToken(@NonNull Context context) {
Expand Down
@@ -0,0 +1,45 @@
/*
* MainThreadExecutor.java
*
* Copyright (c) 2018 Button, Inc. (https://usebutton.com)
*
* 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.usebutton.merchant;

import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;

import java.util.concurrent.Executor;

/**
* Ensures that a given {@link Runnable} is posted and ran on the main thread.
*/
final class MainThreadExecutor implements Executor {

private final Handler handler = new Handler(Looper.getMainLooper());

@Override
public void execute(@NonNull Runnable command) {
handler.post(command);
}
}
Expand Up @@ -27,6 +27,7 @@

import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;

import com.usebutton.merchant.exception.ApplicationIdNotFoundException;
import com.usebutton.merchant.exception.ButtonNetworkException;
Expand All @@ -37,6 +38,8 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.concurrent.Executor;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
Expand All @@ -54,10 +57,12 @@
public class ButtonInternalImplTest {

private ButtonInternalImpl buttonInternal;
private Executor executor;

@Before
public void setUp() {
buttonInternal = new ButtonInternalImpl();
executor = new TestMainThreadExecutor();
buttonInternal = new ButtonInternalImpl(executor);
}

@Test
Expand Down Expand Up @@ -422,4 +427,12 @@ public void handlePostInstallIntent_checkedDeferredDeepLink_doNotUpdateCheckDefe
verify(postInstallIntentListener).onResult((Intent) isNull(), (Throwable) isNull());
verify(buttonRepository, never()).updateCheckDeferredDeepLink(anyBoolean());
}

private class TestMainThreadExecutor implements Executor {

@Override
public void execute(@NonNull Runnable command) {
command.run();
}
}
}
@@ -0,0 +1,60 @@
/*
* MainThreadExecutorTest.java
*
* Copyright (c) 2018 Button, Inc. (https://usebutton.com)
*
* 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.usebutton.merchant;

import android.os.Looper;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;

import java.util.concurrent.Executor;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(RobolectricTestRunner.class)
public class MainThreadExecutorTest {

private Executor executor = new MainThreadExecutor();

@Test
public void executor_shouldExecute() throws Exception {
final boolean[] executed = { false };

executor.execute(new Runnable() {
@Override
public void run() {
assertEquals(Looper.myLooper(), Looper.getMainLooper());
executed[0] = true;
}
});

Robolectric.flushForegroundThreadScheduler();
assertTrue(executed[0]);
}
}
Expand Up @@ -87,16 +87,12 @@ class KotlinActivity : AppCompatActivity() {

private fun initAttributionTokenListener() {
ButtonMerchant.addAttributionTokenListener(this) { token ->
runOnUiThread {
findViewById<TextView>(id.attribution_token).text = token
}
findViewById<TextView>(id.attribution_token).text = token
}
}

private fun toastify(message: String) {
runOnUiThread {
Toast.makeText(this@KotlinActivity, message, Toast.LENGTH_SHORT).show()
}
Toast.makeText(this@KotlinActivity, message, Toast.LENGTH_SHORT).show()
}

companion object {
Expand Down

0 comments on commit e853f0a

Please sign in to comment.