├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── justeat
│ │ └── app
│ │ └── deeplinks
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── justeat
│ │ │ └── app
│ │ │ └── deeplinks
│ │ │ ├── activities
│ │ │ ├── AActivity.java
│ │ │ ├── BActivity.java
│ │ │ ├── CActivity.java
│ │ │ └── LinkDispatcherActivity.java
│ │ │ ├── intents
│ │ │ └── IntentHelper.java
│ │ │ └── links
│ │ │ └── UriToIntentMapper.java
│ └── res
│ │ ├── layout
│ │ ├── activity_a.xml
│ │ ├── activity_b.xml
│ │ └── activity_c.xml
│ │ ├── menu
│ │ └── menu_a.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── justeat
│ └── app
│ └── deeplinks
│ ├── config
│ └── RobolectricRunner.java
│ └── links
│ ├── DeepLinkResolutionTest.java
│ ├── DeepLinks.java
│ └── IntentFilterTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | **/.apt_generated/
3 | **/bin/
4 | **/gen/
5 | **/build/
6 | **/src-gen/
7 | **/java-gen/
8 | **/.idea/
9 | **/.gradle/
10 | **/local.properties
11 | **/*.iml
12 | **/*.iws
13 | **/*.ipr
14 | **/proguard.map
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 JUST EAT plc
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android.Samples.Deeplinks
2 | Sample app presenting a code structure for effective deep linking in Android.
3 |
4 | The project also showcases unit tests for the deep linking functionality.
5 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | repositories {
4 | maven {
5 | url "https://oss.sonatype.org/content/repositories/snapshots"
6 | }
7 | mavenCentral()
8 | }
9 |
10 | android {
11 | compileSdkVersion 22
12 | buildToolsVersion "22.0.1"
13 |
14 | defaultConfig {
15 | applicationId "com.justeat.app.deeplinks"
16 | minSdkVersion 10
17 | targetSdkVersion 22
18 | versionCode 1
19 | versionName "1.0"
20 | }
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | }
28 |
29 | dependencies {
30 | compile 'com.jakewharton:butterknife:6.1.0'
31 |
32 | // Unit Tests
33 | testCompile 'junit:junit:4.12'
34 |
35 | testCompile 'org.hamcrest:hamcrest-core:1.1'
36 | testCompile ('org.hamcrest:hamcrest-integration:1.1') {
37 | exclude module: 'hamcrest-core'
38 | }
39 | testCompile ('org.hamcrest:hamcrest-library:1.1') {
40 | exclude module: 'hamcrest-core'
41 | }
42 | testCompile 'org.mockito:mockito-core:1.9.5'
43 |
44 | testCompile('org.robolectric:robolectric:3.0-SNAPSHOT') {
45 | exclude group: 'commons-logging', module: 'commons-logging'
46 | exclude group: 'org.apache.httpcomponents', module: 'httpclient'
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/23.0.2/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/justeat/app/deeplinks/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/justeat/app/deeplinks/activities/AActivity.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.activities;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.Menu;
6 | import android.view.MenuItem;
7 | import android.widget.EditText;
8 |
9 | import com.justeat.app.deeplinks.R;
10 | import com.justeat.app.deeplinks.intents.IntentHelper;
11 |
12 | import butterknife.ButterKnife;
13 | import butterknife.InjectView;
14 | import butterknife.OnClick;
15 |
16 | public class AActivity extends Activity {
17 |
18 | @InjectView(R.id.query) EditText mQueryEditText;
19 | private IntentHelper mIntents = new IntentHelper();
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_a);
25 | ButterKnife.inject(this);
26 | }
27 |
28 | @OnClick(R.id.button_go)
29 | public void onClick() {
30 | startActivity(mIntents.newBActivityIntent(this, mQueryEditText.getText().toString()));
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/justeat/app/deeplinks/activities/BActivity.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.activities;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.widget.TextView;
9 |
10 | import com.justeat.app.deeplinks.R;
11 | import com.justeat.app.deeplinks.intents.IntentHelper;
12 |
13 | import butterknife.ButterKnife;
14 | import butterknife.InjectView;
15 | import butterknife.OnClick;
16 |
17 | public class BActivity extends Activity {
18 |
19 | @InjectView(R.id.query_label) TextView mQueryText;
20 | private IntentHelper mIntents = new IntentHelper();
21 |
22 | private String mQuery;
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_b);
28 |
29 | ButterKnife.inject(this);
30 |
31 | // Parse intent arguments
32 | parseIntent();
33 |
34 | // Do something with the arguments received
35 | mQueryText.setText(mQuery);
36 | }
37 |
38 | private void parseIntent() {
39 | final Intent intent = getIntent();
40 | final Uri uri = intent.getData();
41 | if (uri != null) {
42 | if ("example-scheme".equals(uri.getScheme()) && "b".equals(uri.getHost())) {
43 | // Cool, we have a URI addressed to this activity!
44 | mQuery = uri.getQueryParameter("query");
45 | }
46 | }
47 |
48 | if (mQuery == null) {
49 | mQuery = intent.getStringExtra(IntentHelper.EXTRA_B_QUERY);
50 | }
51 |
52 | }
53 |
54 | @OnClick({R.id.button_choice1, R.id.button_choice2})
55 | public void onClick(View view) {
56 | int choice;
57 | if (view.getId() == R.id.button_choice1) {
58 | choice = 1;
59 | } else {
60 | choice = 2;
61 | }
62 |
63 | startActivity(mIntents.newCActivityIntent(this, mQuery, choice));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/justeat/app/deeplinks/activities/CActivity.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.activities;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.Menu;
7 | import android.view.MenuItem;
8 | import android.widget.TextView;
9 |
10 | import com.justeat.app.deeplinks.R;
11 | import com.justeat.app.deeplinks.intents.IntentHelper;
12 |
13 | import butterknife.ButterKnife;
14 | import butterknife.InjectView;
15 |
16 | public class CActivity extends Activity {
17 |
18 | @InjectView(R.id.query_label) TextView mQueryTextView;
19 | @InjectView(R.id.choice_label) TextView mChoiceTextView;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_c);
25 | ButterKnife.inject(this);
26 |
27 | parseIntent();
28 | }
29 |
30 | private void parseIntent() {
31 | Intent intent = getIntent();
32 | mQueryTextView.setText(intent.getStringExtra(IntentHelper.EXTRA_C_QUERY));
33 | mChoiceTextView.setText(Integer.toString(intent.getIntExtra(IntentHelper.EXTRA_C_CHOICE, 0)));
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/justeat/app/deeplinks/activities/LinkDispatcherActivity.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.activities;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 |
7 | import com.justeat.app.deeplinks.BuildConfig;
8 | import com.justeat.app.deeplinks.intents.IntentHelper;
9 | import com.justeat.app.deeplinks.links.UriToIntentMapper;
10 |
11 | public class LinkDispatcherActivity extends Activity {
12 | private final UriToIntentMapper mMapper = new UriToIntentMapper(this, new IntentHelper());
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 |
18 | try {
19 | mMapper.dispatchIntent(getIntent());
20 |
21 | } catch (IllegalArgumentException iae) {
22 | // Malformed URL
23 | if (BuildConfig.DEBUG) {
24 | Log.e("Deep links", "Invalid URI", iae);
25 | }
26 | } finally {
27 | // Always finish the Activity so that it doesn't stay in our history
28 | finish();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/justeat/app/deeplinks/intents/IntentHelper.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.intents;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import com.justeat.app.deeplinks.activities.AActivity;
7 | import com.justeat.app.deeplinks.activities.BActivity;
8 | import com.justeat.app.deeplinks.activities.CActivity;
9 |
10 | public class IntentHelper {
11 |
12 | public static String EXTRA_B_QUERY = "com.justeat.app.deeplinks.intents.Intents.EXTRA_B_QUERY";
13 | public static String EXTRA_C_QUERY = "com.justeat.app.deeplinks.intents.Intents.EXTRA_C_QUERY";
14 | public static String EXTRA_C_CHOICE = "com.justeat.app.deeplinks.intents.Intents.EXTRA_C_CHOICE";
15 |
16 | public Intent newAActivityIntent(Context context) {
17 | Intent i = new Intent(context, AActivity.class);
18 |
19 | i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
20 |
21 | return i;
22 | }
23 |
24 | public Intent newBActivityIntent(Context context, String query) {
25 | Intent i = new Intent(context, BActivity.class);
26 |
27 | i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
28 |
29 | i.putExtra(EXTRA_B_QUERY, query);
30 | return i;
31 | }
32 |
33 | public Intent newCActivityIntent(Context context, String query, int choice) {
34 | Intent i = new Intent(context, CActivity.class);
35 |
36 | i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
37 |
38 | i.putExtra(EXTRA_C_QUERY, query);
39 | i.putExtra(EXTRA_C_CHOICE, choice);
40 | return i;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/justeat/app/deeplinks/links/UriToIntentMapper.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.links;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 |
7 | import com.justeat.app.deeplinks.intents.IntentHelper;
8 |
9 | public class UriToIntentMapper {
10 | private Context mContext;
11 | private IntentHelper mIntents;
12 |
13 | public UriToIntentMapper(Context context, IntentHelper intentHelper) {
14 | mContext = context;
15 | mIntents = intentHelper;
16 | }
17 |
18 | public void dispatchIntent(Intent intent) {
19 | final Uri uri = intent.getData();
20 | Intent dispatchIntent = null;
21 |
22 | if (uri == null) throw new IllegalArgumentException("Uri cannot be null");
23 |
24 | final String scheme = uri.getScheme().toLowerCase();
25 | final String host = uri.getHost().toLowerCase();
26 |
27 | if ("example-scheme".equals(scheme)) {
28 | dispatchIntent = mapAppLink(uri);
29 | } else if (("http".equals(scheme) || "https".equals(scheme)) &&
30 | ("www.example.co.uk".equals(host) || "example.co.uk".equals(host))) {
31 | dispatchIntent = mapWebLink(uri);
32 | }
33 |
34 | if (dispatchIntent != null) {
35 | mContext.startActivity(dispatchIntent);
36 | }
37 | }
38 |
39 | private Intent mapAppLink(Uri uri) {
40 | final String host = uri.getHost().toLowerCase();
41 |
42 | switch (host) {
43 | case "activitya":
44 | return mIntents.newAActivityIntent(mContext);
45 | case "activityb":
46 | String bQuery = uri.getQueryParameter("query");
47 | return mIntents.newBActivityIntent(mContext, bQuery);
48 | case "activityc":
49 | String cQuery = uri.getQueryParameter("query");
50 | int choice = Integer.parseInt(uri.getQueryParameter("choice"));
51 | return mIntents.newCActivityIntent(mContext, cQuery, choice);
52 | }
53 | return null;
54 | }
55 |
56 | private Intent mapWebLink(Uri uri) {
57 | final String path = uri.getPath();
58 |
59 | switch (path) {
60 | case "/a":
61 | return mIntents.newAActivityIntent(mContext);
62 | case "/c":
63 | String cQuery = uri.getQueryParameter("query");
64 | int choice = Integer.parseInt(uri.getQueryParameter("choice"));
65 | return mIntents.newCActivityIntent(mContext, cQuery, choice);
66 | }
67 | return null;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_a.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 |
17 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_b.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
19 |
20 |
25 |
26 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_c.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
19 |
20 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_a.xml:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justeat/Android.Samples.Deeplinks/7e0d1ed3144476069f4d3a23dba31410e9318ab3/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justeat/Android.Samples.Deeplinks/7e0d1ed3144476069f4d3a23dba31410e9318ab3/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justeat/Android.Samples.Deeplinks/7e0d1ed3144476069f4d3a23dba31410e9318ab3/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justeat/Android.Samples.Deeplinks/7e0d1ed3144476069f4d3a23dba31410e9318ab3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Deep links
3 |
4 | Activity A
5 | Activity B
6 | Activity C
7 | Settings
8 | Go
9 | Choice 1
10 | Choice 2
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/justeat/app/deeplinks/config/RobolectricRunner.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.config;
2 |
3 | import org.junit.runners.model.InitializationError;
4 | import org.robolectric.RobolectricGradleTestRunner;
5 | import org.robolectric.annotation.Config;
6 | import org.robolectric.manifest.AndroidManifest;
7 | import org.robolectric.res.FileFsFile;
8 | import org.robolectric.res.FsFile;
9 |
10 | /**
11 | * More dynamic path resolution.
12 | *
13 | * This workaround is only for Mac Users necessary and only if they don't use the $MODULE_DIR$
14 | * workaround mentioned at http://robolectric.org/getting-started/.
15 | *
16 | * Follow this issue at https://code.google.com/p/android/issues/detail?id=158015
17 | */
18 | public class RobolectricRunner extends RobolectricGradleTestRunner {
19 |
20 | public RobolectricRunner(Class> klass) throws InitializationError {
21 | super(klass);
22 |
23 | }
24 |
25 | protected AndroidManifest getAppManifest(Config config) {
26 | AndroidManifest appManifest = super.getAppManifest(config);
27 | FsFile androidManifestFile = appManifest.getAndroidManifestFile();
28 | appManifest.setPackageName("com.justeat.app.deeplinks");
29 |
30 | if (androidManifestFile.exists()) {
31 | return appManifest;
32 | } else {
33 | String moduleRoot = getModuleRootPath(config);
34 | androidManifestFile = FileFsFile.from(moduleRoot, appManifest.getAndroidManifestFile().getPath().replace("bundles", "manifests/full"));
35 | FsFile resDirectory = FileFsFile.from(moduleRoot, appManifest.getResDirectory().getPath().replace("/res", "").replace("bundles", "res"));
36 | FsFile assetsDirectory = FileFsFile.from(moduleRoot, appManifest.getAssetsDirectory().getPath().replace("/assets", "").replace("bundles", "assets"));
37 | return new AndroidManifest(androidManifestFile, resDirectory, assetsDirectory);
38 | }
39 | }
40 |
41 | private String getModuleRootPath(Config config) {
42 | String moduleRoot = config.constants().getResource("").toString().replace("file:", "");
43 | return moduleRoot.substring(0, moduleRoot.indexOf("/build"));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/test/java/com/justeat/app/deeplinks/links/DeepLinkResolutionTest.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.links;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 |
8 | import com.justeat.app.deeplinks.BuildConfig;
9 | import com.justeat.app.deeplinks.activities.LinkDispatcherActivity;
10 | import com.justeat.app.deeplinks.config.RobolectricRunner;
11 | import com.justeat.app.deeplinks.intents.IntentHelper;
12 |
13 | import org.junit.Before;
14 | import org.junit.Test;
15 | import org.junit.runner.RunWith;
16 | import org.robolectric.annotation.Config;
17 |
18 | import static org.mockito.Matchers.any;
19 | import static org.mockito.Matchers.eq;
20 | import static org.mockito.Mockito.mock;
21 | import static org.mockito.Mockito.verify;
22 | import static org.mockito.Mockito.when;
23 |
24 | @Config(constants = BuildConfig.class, sdk = 21)
25 | @RunWith(RobolectricRunner.class)
26 | public class DeepLinkResolutionTest {
27 |
28 | private IntentHelper mMockIntents;
29 | private Activity mMockActivity;
30 | private UriToIntentMapper mMapper;
31 |
32 | @Before
33 | public void setUp() {
34 | mMockIntents = mock(IntentHelper.class);
35 | mMockActivity = mock(LinkDispatcherActivity.class);
36 | mMapper = new UriToIntentMapper(mMockActivity, mMockIntents);
37 | }
38 |
39 | @Test
40 | public void activity_a_link_dispatches() {
41 | //Arrange
42 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityAUri()));
43 | Intent mockDispatchIntent = mock(Intent.class);
44 | when(mMockIntents.newAActivityIntent(mMockActivity)).thenReturn(mockDispatchIntent);
45 |
46 | //Act
47 | mMapper.dispatchIntent(intent);
48 |
49 | //Assert
50 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
51 | }
52 |
53 | @Test
54 | public void activity_b_link_dispatches() {
55 | //Arrange
56 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityBUri("bogo")));
57 | Intent mockDispatchIntent = mock(Intent.class);
58 | when(mMockIntents.newBActivityIntent(eq(mMockActivity), eq("bogo"))).thenReturn(mockDispatchIntent);
59 |
60 | //Act
61 | mMapper.dispatchIntent(intent);
62 |
63 | //Assert
64 | verify(mMockIntents).newBActivityIntent(any(Context.class), eq("bogo"));
65 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
66 | }
67 |
68 | @Test
69 | public void activity_c_link_dispatches() {
70 | //Arrange
71 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityCUri("bogo", 1234)));
72 | Intent mockDispatchIntent = mock(Intent.class);
73 | when(mMockIntents.newCActivityIntent(eq(mMockActivity), eq("bogo"), eq(1234))).thenReturn(mockDispatchIntent);
74 |
75 | //Act
76 | mMapper.dispatchIntent(intent);
77 |
78 | //Assert
79 | verify(mMockIntents).newCActivityIntent(any(Context.class), eq("bogo"), eq(1234));
80 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
81 | }
82 |
83 | @Test
84 | public void activity_a_web_link_http_dispatches() {
85 | //Arrange
86 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityAWebUrl(false, false)));
87 | Intent mockDispatchIntent = mock(Intent.class);
88 | when(mMockIntents.newAActivityIntent(mMockActivity)).thenReturn(mockDispatchIntent);
89 |
90 | //Act
91 | mMapper.dispatchIntent(intent);
92 |
93 | //Assert
94 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
95 | }
96 |
97 | @Test
98 | public void activity_a_web_link_http_www_dispatches() {
99 | //Arrange
100 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityAWebUrl(false, true)));
101 | Intent mockDispatchIntent = mock(Intent.class);
102 | when(mMockIntents.newAActivityIntent(mMockActivity)).thenReturn(mockDispatchIntent);
103 |
104 | //Act
105 | mMapper.dispatchIntent(intent);
106 |
107 | //Assert
108 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
109 | }
110 |
111 | @Test
112 | public void activity_a_web_link_https_dispatches() {
113 | //Arrange
114 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityAWebUrl(true, false)));
115 | Intent mockDispatchIntent = mock(Intent.class);
116 | when(mMockIntents.newAActivityIntent(mMockActivity)).thenReturn(mockDispatchIntent);
117 |
118 | //Act
119 | mMapper.dispatchIntent(intent);
120 |
121 | //Assert
122 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
123 | }
124 |
125 | @Test
126 | public void activity_a_web_link_https_www_dispatches() {
127 | //Arrange
128 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityAWebUrl(true, true)));
129 | Intent mockDispatchIntent = mock(Intent.class);
130 | when(mMockIntents.newAActivityIntent(mMockActivity)).thenReturn(mockDispatchIntent);
131 |
132 | //Act
133 | mMapper.dispatchIntent(intent);
134 |
135 | //Assert
136 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
137 | }
138 |
139 | @Test
140 | public void activity_c_web_link_http_dispatches() {
141 | //Arrange
142 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityCWebUrl(false, false, "bogo", 1234)));
143 | Intent mockDispatchIntent = mock(Intent.class);
144 | when(mMockIntents.newCActivityIntent(eq(mMockActivity), eq("bogo"), eq(1234))).thenReturn(mockDispatchIntent);
145 |
146 | //Act
147 | mMapper.dispatchIntent(intent);
148 |
149 | //Assert
150 | verify(mMockIntents).newCActivityIntent(any(Context.class), eq("bogo"), eq(1234));
151 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
152 | }
153 |
154 | @Test
155 | public void activity_c_web_link_http_www_dispatches() {
156 | //Arrange
157 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityCWebUrl(false, true, "bogo", 1234)));
158 | Intent mockDispatchIntent = mock(Intent.class);
159 | when(mMockIntents.newCActivityIntent(eq(mMockActivity), eq("bogo"), eq(1234))).thenReturn(mockDispatchIntent);
160 |
161 | //Act
162 | mMapper.dispatchIntent(intent);
163 |
164 | //Assert
165 | verify(mMockIntents).newCActivityIntent(any(Context.class), eq("bogo"), eq(1234));
166 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
167 | }
168 |
169 | @Test
170 | public void activity_c_web_link_https_dispatches() {
171 | //Arrange
172 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityCWebUrl(true, false, "bogo", 1234)));
173 | Intent mockDispatchIntent = mock(Intent.class);
174 | when(mMockIntents.newCActivityIntent(eq(mMockActivity), eq("bogo"), eq(1234))).thenReturn(mockDispatchIntent);
175 |
176 | //Act
177 | mMapper.dispatchIntent(intent);
178 |
179 | //Assert
180 | verify(mMockIntents).newCActivityIntent(any(Context.class), eq("bogo"), eq(1234));
181 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
182 | }
183 |
184 | @Test
185 | public void activity_c_web_link_https_www_dispatches() {
186 | //Arrange
187 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DeepLinks.getActivityCWebUrl(true, true, "bogo", 1234)));
188 | Intent mockDispatchIntent = mock(Intent.class);
189 | when(mMockIntents.newCActivityIntent(eq(mMockActivity), eq("bogo"), eq(1234))).thenReturn(mockDispatchIntent);
190 |
191 | //Act
192 | mMapper.dispatchIntent(intent);
193 |
194 | //Assert
195 | verify(mMockIntents).newCActivityIntent(any(Context.class), eq("bogo"), eq(1234));
196 | verify(mMockActivity).startActivity(eq(mockDispatchIntent));
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/app/src/test/java/com/justeat/app/deeplinks/links/DeepLinks.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.links;
2 |
3 | public class DeepLinks {
4 |
5 | public static String getActivityAUri() {
6 | return "example-scheme://activitya";
7 | }
8 |
9 | public static String getActivityBUri(String query) {
10 | return String.format("example-scheme://activityb?query=%s", query);
11 | }
12 |
13 | public static String getActivityCUri(String query, int choice) {
14 | return String.format("example-scheme://activityc?query=%s&choice=%d", query, choice);
15 | }
16 |
17 | public static String getActivityAWebUrl(boolean https, boolean www) {
18 | StringBuilder sb = new StringBuilder(https ? "https" : "http");
19 | sb.append("://");
20 | if (www) sb.append("www.");
21 | sb.append("example.co.uk");
22 | sb.append("/a");
23 | return sb.toString();
24 | }
25 |
26 | public static String getActivityCWebUrl(boolean https, boolean www, String query, int choice) {
27 | StringBuilder sb = new StringBuilder(https ? "https" : "http");
28 | sb.append("://");
29 | if (www) sb.append("www.");
30 | sb.append("example.co.uk");
31 | sb.append(String.format("/c?query=%s&choice=%d", query, choice));
32 | return sb.toString();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/test/java/com/justeat/app/deeplinks/links/IntentFilterTest.java:
--------------------------------------------------------------------------------
1 | package com.justeat.app.deeplinks.links;
2 |
3 | import android.content.Intent;
4 | import android.content.IntentFilter;
5 | import android.content.pm.ActivityInfo;
6 | import android.content.pm.ResolveInfo;
7 | import android.net.Uri;
8 | import android.os.PatternMatcher;
9 | import android.text.TextUtils;
10 |
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.robolectric.RuntimeEnvironment;
15 | import org.robolectric.annotation.Config;
16 | import org.robolectric.manifest.ActivityData;
17 | import org.robolectric.manifest.AndroidManifest;
18 | import org.robolectric.manifest.IntentFilterData;
19 | import org.robolectric.res.builder.RobolectricPackageManager;
20 | import org.robolectric.shadows.ShadowApplication;
21 |
22 | import com.justeat.app.deeplinks.BuildConfig;
23 | import com.justeat.app.deeplinks.activities.LinkDispatcherActivity;
24 | import com.justeat.app.deeplinks.config.RobolectricRunner;
25 |
26 | import java.util.ArrayList;
27 | import java.util.List;
28 | import java.util.Map;
29 |
30 | import static org.hamcrest.Matchers.isOneOf;
31 | import static org.junit.Assert.assertThat;
32 |
33 | @Config(constants = BuildConfig.class, sdk = 21)
34 | @RunWith(RobolectricRunner.class)
35 | public class IntentFilterTest {
36 | @Test
37 | public void activity_a_app_link_resolves() {
38 | assertIntentResolves(DeepLinks.getActivityAUri());
39 | }
40 |
41 | @Test
42 | public void activity_b_app_link_resolves() {
43 | assertIntentResolves(DeepLinks.getActivityBUri("bogoQuery"));
44 | }
45 |
46 | @Test
47 | public void activity_c_app_link_resolves() {
48 | assertIntentResolves(DeepLinks.getActivityCUri("bogoQuery", 3));
49 | }
50 |
51 | @Test
52 | public void activity_a_web_link_resolves_http() {
53 | assertIntentResolves(DeepLinks.getActivityAWebUrl(false, false));
54 | }
55 |
56 | @Test
57 | public void activity_a_web_link_resolves_http_www() {
58 | assertIntentResolves(DeepLinks.getActivityAWebUrl(false, true));
59 | }
60 |
61 | @Test
62 | public void activity_a_web_link_resolves_https() {
63 | assertIntentResolves(DeepLinks.getActivityAWebUrl(true, false));
64 | }
65 |
66 | @Test
67 | public void activity_a_web_link_resolves_https_www() {
68 | assertIntentResolves(DeepLinks.getActivityAWebUrl(true, true));
69 | }
70 |
71 | @Test
72 | public void activity_c_web_link_resolves_http() {
73 | assertIntentResolves(DeepLinks.getActivityCWebUrl(false, false, "bogoQuery", 3));
74 | }
75 |
76 | @Test
77 | public void activity_c_web_link_resolves_http_www() {
78 | assertIntentResolves(DeepLinks.getActivityCWebUrl(false, true, "bogoQuery", 3));
79 | }
80 |
81 | @Test
82 | public void activity_c_web_link_resolves_https() {
83 | assertIntentResolves(DeepLinks.getActivityCWebUrl(true, false, "bogoQuery", 3));
84 | }
85 |
86 | @Test
87 | public void activity_c_web_link_resolves_https_www() {
88 | assertIntentResolves(DeepLinks.getActivityCWebUrl(true, true, "bogoQuery", 3));
89 | }
90 |
91 |
92 | private void assertIntentResolves(String uriString) {
93 | //Arrange
94 | ShadowApplication.getInstance().getAppManifest();
95 | RobolectricPackageManager packageManager = (RobolectricPackageManager) RuntimeEnvironment.application.getPackageManager();
96 | packageManager.setQueryIntentImplicitly(true);
97 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriString));
98 |
99 | //Act
100 | List resolveInfo = queryImplicitIntent(intent, -1);
101 | ArrayList activityNames = new ArrayList<>();
102 | for (ResolveInfo info : resolveInfo) {
103 | activityNames.add(info.activityInfo.targetActivity);
104 | }
105 |
106 | //Assert
107 | assertThat(LinkDispatcherActivity.class.getName(), isOneOf(activityNames.toArray()));
108 | }
109 |
110 | private List queryImplicitIntent(Intent intent, int flags) {
111 | List resolveInfoList = new ArrayList();
112 | AndroidManifest appManifest = ShadowApplication.getInstance().getAppManifest();
113 | String packageName = appManifest.getPackageName();
114 |
115 | for (Map.Entry activity : appManifest.getActivityDatas().entrySet()) {
116 | String activityName = activity.getKey();
117 | ActivityData activityData = activity.getValue();
118 | if (activityData.getTargetActivity() != null) {
119 | activityName = activityData.getTargetActivityName();
120 | }
121 |
122 | if (matchIntentFilter(activityData, intent)) {
123 | ResolveInfo resolveInfo = new ResolveInfo();
124 | resolveInfo.resolvePackageName = packageName;
125 | resolveInfo.activityInfo = new ActivityInfo();
126 | resolveInfo.activityInfo.targetActivity = activityName;
127 |
128 | resolveInfoList.add(resolveInfo);
129 | }
130 | }
131 |
132 | return resolveInfoList;
133 | }
134 |
135 | private boolean matchIntentFilter(ActivityData activityData, Intent intent) {
136 | System.out.println("Searching for "+ intent.getDataString());
137 | for (IntentFilterData intentFilterData : activityData.getIntentFilters()) {
138 | List actionList = intentFilterData.getActions();
139 | List categoryList = intentFilterData.getCategories();
140 | IntentFilter intentFilter = new IntentFilter();
141 |
142 | for (String action : actionList) {
143 | intentFilter.addAction(action);
144 | }
145 |
146 | for (String category : categoryList) {
147 | intentFilter.addCategory(category);
148 | }
149 |
150 | for (String scheme : intentFilterData.getSchemes()) {
151 | intentFilter.addDataScheme(scheme);
152 | }
153 |
154 | for (String mimeType : intentFilterData.getMimeTypes()) {
155 | try {
156 | intentFilter.addDataType(mimeType);
157 | } catch (IntentFilter.MalformedMimeTypeException ex) {
158 | throw new RuntimeException(ex);
159 | }
160 | }
161 |
162 | for (String path : intentFilterData.getPaths()) {
163 | intentFilter.addDataPath(path, PatternMatcher.PATTERN_LITERAL);
164 | }
165 |
166 | for (String pathPattern : intentFilterData.getPathPatterns()) {
167 | intentFilter.addDataPath(pathPattern, PatternMatcher.PATTERN_SIMPLE_GLOB);
168 | }
169 |
170 | for (String pathPrefix : intentFilterData.getPathPrefixes()) {
171 | intentFilter.addDataPath(pathPrefix, PatternMatcher.PATTERN_PREFIX);
172 | }
173 |
174 | for (IntentFilterData.DataAuthority authority : intentFilterData.getAuthorities()) {
175 | intentFilter.addDataAuthority(authority.getHost(), authority.getPort());
176 | }
177 |
178 | // match action
179 | boolean matchActionResult = intentFilter.matchAction(intent.getAction());
180 | // match category
181 | String matchCategoriesResult = intentFilter.matchCategories(intent.getCategories());
182 | // match data
183 | int matchResult = intentFilter.matchData(intent.getType(), intent.getScheme(),
184 | intent.getData());
185 |
186 | if ((matchResult != IntentFilter.NO_MATCH_DATA && matchResult != IntentFilter.NO_MATCH_TYPE)) {
187 | System.out.println("Matcher result for " + intent.getType() + " " + intent.getScheme() + " " +
188 | intent.getDataString() +": " + Integer.toHexString(matchResult));
189 | System.out.println(intentFilterData.getSchemes());
190 | for (IntentFilterData.DataAuthority authority : intentFilterData.getAuthorities()) {
191 | System.out.println(authority.getHost() + ":" + authority.getPort());
192 | }
193 | System.out.println(intentFilterData.getPaths());
194 | System.out.println(intentFilterData.getPathPatterns());
195 | System.out.println(intentFilterData.getPathPrefixes());
196 | boolean pathMatchesExactly = false;
197 | for (int i = 0; i < intentFilter.countDataPaths(); i++) {
198 | PatternMatcher pm = intentFilter.getDataPath(i);
199 | if (pm.match(intent.getData().getPath())) {
200 | System.out.println("Pattern match on path: " + pm.getPath());
201 | }
202 | }
203 | }
204 |
205 | // Check if it's a match at all
206 | boolean result = matchActionResult && (matchCategoriesResult == null) &&
207 | (matchResult != IntentFilter.NO_MATCH_DATA && matchResult != IntentFilter.NO_MATCH_TYPE);
208 |
209 | // No match, discard activity
210 | if (!result) continue;
211 |
212 | // We have a partial match. The matchResult doesn't seem to correspond to reality
213 | // as far as path matching goes - no idea why. Fortunately we can check if the path
214 | // matches exactly manually.
215 | if (!TextUtils.isEmpty(intent.getData().getPath())) {
216 | //If the path is not empty, we need to make sure it's an exact match
217 |
218 | boolean pathMatch = false;
219 | for (int i = 0; i < intentFilter.countDataPaths(); i++) {
220 | PatternMatcher pm = intentFilter.getDataPath(i);
221 | if (pm.match(intent.getData().getPath())) {
222 | // Exact match found, return
223 | pathMatch = true;
224 | }
225 | }
226 | // No exact match found - we only have a partial match on the intent filter.
227 | // While this filter may work for general cases,
228 | // for links like "android-app://com.justeat.app.uk/just-eat.co.uk/account"
229 | // to work, the path must be an exact match. Hence we enforce this for all intents
230 | // This way we can catch all errors.
231 | result = pathMatch;
232 | }
233 | if (result) return true;
234 | }
235 | return false;
236 | }
237 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.2.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justeat/Android.Samples.Deeplinks/7e0d1ed3144476069f4d3a23dba31410e9318ab3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 16 11:18:55 BST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------