Skip to content

Commit fc7bada

Browse files
authored
feat: Try to catch invalid map api keys in the demo (#1400)
* feat: Try to catch invalid map api keys in the demo feat: Export the demo activities so they can be run directly chore: Update gradle version, Google maps to 19.0.0 * fix: Add copyright header. Add testing * fix: Copyright header error * fix: Copyright header error for strings.xml * fix: Correct copyright attribution * fix: class name typo chore: remove unused robolectric dependency
1 parent dd85f6f commit fc7bada

File tree

12 files changed

+215
-51
lines changed

12 files changed

+215
-51
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
buildscript {
18-
ext.kotlin_version = '1.9.24'
18+
ext.kotlin_version = '2.0.0'
1919
repositories {
2020
google()
2121
mavenCentral()
@@ -24,7 +24,7 @@ buildscript {
2424
}
2525
}
2626
dependencies {
27-
classpath 'com.android.tools.build:gradle:8.4.0'
27+
classpath 'com.android.tools.build:gradle:8.7.0'
2828
classpath 'com.mxalbert.gradle:jacoco-android:0.2.1'
2929
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
3030
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

demo/build.gradle

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,16 @@ dependencies {
5353
// [START_EXCLUDE silent]
5454
implementation project(':library')
5555

56-
implementation 'androidx.appcompat:appcompat:1.7.0-beta01'
56+
implementation 'androidx.appcompat:appcompat:1.7.0'
5757
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
5858

59-
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3"
59+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6"
6060
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
6161

6262
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
6363
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1'
64-
64+
testImplementation("junit:junit:4.13.2")
65+
testImplementation("com.google.truth:truth:1.4.2")
6566
// [END_EXCLUDE]
6667
}
6768
// [END maps_android_utils_install_snippet]

demo/src/main/AndroidManifest.xml

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
2-
<!--
1+
<?xml version="1.0" encoding="utf-8" standalone="no"?><!--
32
Copyright 2024 Google LLC
43
54
Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,7 +13,6 @@
1413
See the License for the specific language governing permissions and
1514
limitations under the License.
1615
-->
17-
1816
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
1917
xmlns:tools="http://schemas.android.com/tools">
2018

@@ -58,35 +56,72 @@
5856
android:value="${MAPS_API_KEY}" />
5957

6058
<activity
61-
android:exported="true"
6259
android:name=".MainActivity"
63-
android:label="@string/app_name">
60+
android:exported="true">
6461
<intent-filter>
6562
<action android:name="android.intent.action.MAIN" />
6663
<category android:name="android.intent.category.LAUNCHER" />
6764
</intent-filter>
6865
</activity>
6966

70-
<activity android:name=".PolyDecodeDemoActivity" />
71-
<activity android:name=".PolySimplifyDemoActivity" />
72-
<activity android:name=".IconGeneratorDemoActivity" />
73-
<activity android:name=".DistanceDemoActivity" />
74-
<activity android:name=".ClusteringDemoActivity" />
75-
<activity android:name=".BigClusteringDemoActivity" />
76-
<activity android:name=".VisibleClusteringDemoActivity" />
77-
<activity android:name=".CustomMarkerClusteringDemoActivity" />
78-
<activity android:name=".CustomAdvancedMarkerClusteringDemoActivity" />
79-
<activity android:name=".ZoomClusteringDemoActivity" />
80-
<activity android:name=".ClusteringViewModelDemoActivity"/>
81-
<activity android:name=".TileProviderAndProjectionDemo" />
82-
<activity android:name=".HeatmapsDemoActivity" />
83-
<activity android:name=".HeatmapsPlacesDemoActivity" />
84-
<activity android:name=".GeoJsonDemoActivity" />
85-
<activity android:name=".KmlDemoActivity" />
86-
<activity android:name=".MultiLayerDemoActivity" />
87-
<activity android:name=".AnimationUtilDemoActivity" />
88-
<activity android:name=".StreetViewDemoActivity" />
67+
<activity
68+
android:name=".PolyDecodeDemoActivity"
69+
android:exported="true" />
70+
<activity
71+
android:name=".PolySimplifyDemoActivity"
72+
android:exported="true" />
73+
<activity
74+
android:name=".IconGeneratorDemoActivity"
75+
android:exported="true" />
76+
<activity
77+
android:name=".DistanceDemoActivity"
78+
android:exported="true" />
79+
<activity
80+
android:name=".ClusteringDemoActivity"
81+
android:exported="true" />
82+
<activity
83+
android:name=".BigClusteringDemoActivity"
84+
android:exported="true" />
85+
<activity
86+
android:name=".VisibleClusteringDemoActivity"
87+
android:exported="true" />
88+
<activity
89+
android:name=".CustomMarkerClusteringDemoActivity"
90+
android:exported="true" />
91+
<activity
92+
android:name=".CustomAdvancedMarkerClusteringDemoActivity"
93+
android:exported="true" />
94+
<activity
95+
android:name=".ZoomClusteringDemoActivity"
96+
android:exported="true" />
97+
<activity
98+
android:name=".ClusteringViewModelDemoActivity"
99+
android:exported="true" />
100+
<activity
101+
android:name=".TileProviderAndProjectionDemo"
102+
android:exported="true" />
103+
<activity
104+
android:name=".HeatmapsDemoActivity"
105+
android:exported="true" />
106+
<activity
107+
android:name=".HeatmapsPlacesDemoActivity"
108+
android:exported="true" />
109+
<activity
110+
android:name=".GeoJsonDemoActivity"
111+
android:exported="true" />
112+
<activity
113+
android:name=".KmlDemoActivity"
114+
android:exported="true" />
115+
<activity
116+
android:name=".MultiLayerDemoActivity"
117+
android:exported="true" />
118+
<activity
119+
android:name=".AnimationUtilDemoActivity"
120+
android:exported="true" />
121+
<activity
122+
android:name=".StreetViewDemoActivity"
123+
android:exported="true" />
89124

90125
</application>
91126

92-
</manifest>
127+
</manifest>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.maps.android.utils.demo
18+
19+
import android.content.Context
20+
import android.content.pm.PackageManager
21+
import java.util.regex.Pattern
22+
23+
/**
24+
* Checks if the provided context has a valid Google Maps API key in its metadata.
25+
*
26+
*
27+
* This method retrieves the API key from the application's metadata and returns whether or
28+
* not it has a valid format.
29+
*
30+
* @param context The context to check for the API key.
31+
* @return `true` if the context has a valid API key, `false` otherwise.
32+
*/
33+
fun hasMapsApiKey(context: Context): Boolean {
34+
val mapsApiKey = getMapsApiKey(context)
35+
36+
return mapsApiKey != null && keyHasValidFormat(mapsApiKey)
37+
}
38+
39+
/**
40+
* Checks if the provided API key has a valid format.
41+
*
42+
*
43+
* The valid format is defined by the regular expression "^AIza[0-9A-Za-z\\-_]{35}$".
44+
*
45+
* @param apiKey The API key to validate.
46+
* @return `true` if the API key has a valid format, `false` otherwise.
47+
*/
48+
internal fun keyHasValidFormat(apiKey: String): Boolean {
49+
val regex = "^AIza[0-9A-Za-z\\-_]{35}$"
50+
val pattern = Pattern.compile(regex)
51+
val matcher = pattern.matcher(apiKey)
52+
return matcher.matches()
53+
}
54+
55+
/**
56+
* Retrieves the Google Maps API key from the application metadata.
57+
*
58+
* @param context The context to retrieve the API key from.
59+
* @return The API key if found, `null` otherwise.
60+
*/
61+
private fun getMapsApiKey(context: Context): String? {
62+
try {
63+
val bundle = context.packageManager
64+
.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
65+
.metaData
66+
return bundle.getString("com.google.android.geo.API_KEY")
67+
} catch (e: PackageManager.NameNotFoundException) {
68+
e.printStackTrace()
69+
return null
70+
}
71+
}

demo/src/main/java/com/google/maps/android/utils/demo/BaseDemoActivity.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package com.google.maps.android.utils.demo;
1818

19+
import static com.google.maps.android.utils.demo.ApiKeyValidatorKt.hasMapsApiKey;
20+
1921
import android.os.Bundle;
22+
import android.widget.Toast;
2023

2124
import androidx.annotation.NonNull;
2225
import androidx.fragment.app.FragmentActivity;
@@ -36,6 +39,12 @@ protected int getLayoutId() {
3639
@Override
3740
public void onCreate(Bundle savedInstanceState) {
3841
super.onCreate(savedInstanceState);
42+
43+
if (!hasMapsApiKey(this)) {
44+
Toast.makeText(this, R.string.bad_maps_api_key, Toast.LENGTH_LONG).show();
45+
finish();
46+
}
47+
3948
mIsRestore = savedInstanceState != null;
4049
setContentView(getLayoutId());
4150
setUpMap();

demo/src/main/java/com/google/maps/android/utils/demo/ClusteringViewModel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
public class ClusteringViewModel extends ViewModel {
3232

33-
private NonHierarchicalViewBasedAlgorithm<MyItem> mAlgorithm = new NonHierarchicalViewBasedAlgorithm<>(0, 0);
33+
private final NonHierarchicalViewBasedAlgorithm<MyItem> mAlgorithm = new NonHierarchicalViewBasedAlgorithm<>(0, 0);
3434

3535
NonHierarchicalViewBasedAlgorithm<MyItem> getAlgorithm() {
3636
return mAlgorithm;

demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.maps.android.utils.demo;
1818

19+
import static com.google.maps.android.utils.demo.ApiKeyValidatorKt.hasMapsApiKey;
20+
1921
import android.app.Activity;
2022
import android.content.Intent;
2123
import android.os.Bundle;
@@ -32,12 +34,14 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
3234
@Override
3335
protected void onCreate(Bundle savedInstanceState) {
3436
super.onCreate(savedInstanceState);
35-
setContentView(R.layout.main);
3637

37-
if (BuildConfig.MAPS_API_KEY.isEmpty()) {
38-
Toast.makeText(this, "Add your own API key in local.properties as MAPS_API_KEY=YOUR_API_KEY", Toast.LENGTH_LONG).show();
38+
if (!hasMapsApiKey(this)) {
39+
Toast.makeText(this, R.string.bad_maps_api_key, Toast.LENGTH_LONG).show();
40+
finish();
3941
}
4042

43+
setContentView(R.layout.main);
44+
4145
mListView = findViewById(R.id.list);
4246

4347
addDemo("Clustering", ClusteringDemoActivity.class);

demo/src/main/java/com/google/maps/android/utils/demo/StreetViewDemoActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.google.maps.android.utils.demo
1919
import android.app.Activity
2020
import android.os.Bundle
2121
import android.widget.TextView
22+
import android.widget.Toast
2223
import com.google.android.gms.maps.model.LatLng
2324
import com.google.maps.android.StreetViewUtils
2425
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -33,6 +34,11 @@ class StreetViewDemoActivity : Activity() {
3334
super.onCreate(savedInstanceState)
3435
setContentView(R.layout.street_view_demo)
3536

37+
if (!hasMapsApiKey(this)) {
38+
Toast.makeText(this, R.string.bad_maps_api_key, Toast.LENGTH_LONG).show()
39+
finish()
40+
}
41+
3642
GlobalScope.launch(Dispatchers.Main) {
3743
val response1 =
3844
StreetViewUtils.fetchStreetViewData(LatLng(48.1425918, 11.5386121), BuildConfig.MAPS_API_KEY)
@@ -43,4 +49,3 @@ class StreetViewDemoActivity : Activity() {
4349
}
4450
}
4551
}
46-

demo/src/main/res/values/strings.xml

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<!--
3-
~ Copyright 2020 Google Inc.
4-
~
5-
~ Licensed under the Apache License, Version 2.0 (the "License");
6-
~ you may not use this file except in compliance with the License.
7-
~ You may obtain a copy of the License at
8-
~
9-
~ http://www.apache.org/licenses/LICENSE-2.0
10-
~
11-
~ Unless required by applicable law or agreed to in writing, software
12-
~ distributed under the License is distributed on an "AS IS" BASIS,
13-
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14-
~ See the License for the specific language governing permissions and
15-
~ limitations under the License.
3+
Copyright 2024 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
1616
-->
1717

1818
<resources>
@@ -33,4 +33,5 @@
3333
<string name="button_radius">Radius</string>
3434
<string name="button_gradient">Gradient</string>
3535
<string name="button_opacity">Opacity</string>
36+
<string name="bad_maps_api_key">Invalid or missing Google Maps API key</string>
3637
</resources>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.maps.android.utils.demo
18+
19+
import org.junit.Test
20+
import com.google.common.truth.Truth.assertThat
21+
22+
class ApiKeyValidatorTest {
23+
24+
@Test
25+
fun testValidKey() {
26+
val apiKey = "AIzaSyBZAuYobWtoFlmuyyG2HxQWatnPJZ79_BW"
27+
assertThat(keyHasValidFormat(apiKey)).isTrue()
28+
}
29+
30+
@Test
31+
fun testInvalidKeys() {
32+
assertThat(keyHasValidFormat("")).isFalse()
33+
assertThat(keyHasValidFormat("YOUR_API_KEY")).isFalse()
34+
val apiKey = "AIzaSyBZAuYobWtoFlmuyyG2HxQWatnPJZ79_BW"
35+
assertThat(keyHasValidFormat(apiKey.dropLast(1))).isFalse()
36+
}
37+
38+
}

0 commit comments

Comments
 (0)