Port FeatureOverview to Kotlin and remove empty activity tests (#686)

This commit is contained in:
Birk Skyum
2023-01-11 17:05:16 +01:00
committed by GitHub
parent 01729b9a49
commit a6f632b4ad
10 changed files with 182 additions and 350 deletions

View File

@@ -259,11 +259,6 @@ run-android-publish: gradle/configuration.gradle
android-gfxinfo:
adb shell dumpsys gfxinfo com.mapbox.mapboxsdk.testapp reset
# Generates Activity sanity tests
.PHONY: test-code-android
test-code-android:
node scripts/generate-test-code.js
# Runs checkstyle and lint on the java code
.PHONY: android-check
android-check : android-ktlint android-checkstyle android-lint-sdk android-lint-test-app run-android-nitpick

View File

@@ -1,26 +0,0 @@
<%
const activity = locals[0];
const subPackage = locals[1];
-%>
// This file is generated. Edit android/platform/scripts/generate-test-code.js, then run `make generate-test-android`.
package com.mapbox.mapboxsdk.testapp.activity.gen.<%- subPackage %>;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import com.mapbox.mapboxsdk.testapp.activity.BaseTest
import com.mapbox.mapboxsdk.testapp.activity.<%- subPackage %>.<%- activity %>
import org.junit.Test
import org.junit.runner.RunWith
/**
* Sanity test for <%- activity %>
*/
@RunWith(AndroidJUnit4ClassRunner::class)
class <%- activity %>Test : BaseTest() {
@Test
fun testSanity() {
}
override fun getActivityClass(): Class<*> {
return <%- activity %>::class.java
}
}

View File

@@ -1,171 +0,0 @@
package com.mapbox.mapboxsdk.testapp.activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.adapter.FeatureAdapter;
import com.mapbox.mapboxsdk.testapp.adapter.FeatureSectionAdapter;
import com.mapbox.mapboxsdk.testapp.model.activity.Feature;
import com.mapbox.mapboxsdk.testapp.utils.ItemClickSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import timber.log.Timber;
/**
* Activity shown when application is started
* <p>
* This activity will generate data for RecyclerView based on the AndroidManifest entries.
* It uses tags as category and description to order the different entries.
* </p>
*/
public class FeatureOverviewActivity extends AppCompatActivity {
private static final String KEY_STATE_FEATURES = "featureList";
private RecyclerView recyclerView;
private FeatureSectionAdapter sectionAdapter;
private List<Feature> features;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feature_overview);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener());
recyclerView.setHasFixedSize(true);
ItemClickSupport.addTo(recyclerView).setOnItemClickListener((recyclerView, position, view) -> {
if (!sectionAdapter.isSectionHeaderPosition(position)) {
int itemPosition = sectionAdapter.getConvertedPosition(position);
Feature feature = features.get(itemPosition);
startFeature(feature);
}
});
if (savedInstanceState == null) {
loadFeatures();
} else {
features = savedInstanceState.getParcelableArrayList(KEY_STATE_FEATURES);
onFeaturesLoaded(features);
}
}
private void loadFeatures() {
try {
new LoadFeatureTask().execute(
getPackageManager().getPackageInfo(getPackageName(),
PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA));
} catch (PackageManager.NameNotFoundException exception) {
Timber.e(exception, "Could not resolve package info");
}
}
private void onFeaturesLoaded(List<Feature> featuresList) {
features = featuresList;
if (featuresList == null || featuresList.isEmpty()) {
return;
}
List<FeatureSectionAdapter.Section> sections = new ArrayList<>();
String currentCat = "";
for (int i = 0; i < features.size(); i++) {
String category = features.get(i).getCategory();
if (!currentCat.equals(category)) {
sections.add(new FeatureSectionAdapter.Section(i, category));
currentCat = category;
}
}
FeatureSectionAdapter.Section[] dummy = new FeatureSectionAdapter.Section[sections.size()];
sectionAdapter = new FeatureSectionAdapter(
this, R.layout.section_main_layout, R.id.section_text, new FeatureAdapter(features));
sectionAdapter.setSections(sections.toArray(dummy));
recyclerView.setAdapter(sectionAdapter);
}
private void startFeature(Feature feature) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(getPackageName(), feature.getName()));
startActivity(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(KEY_STATE_FEATURES, (ArrayList<Feature>) features);
}
private class LoadFeatureTask extends AsyncTask<PackageInfo, Void, List<Feature>> {
@Override
protected List<Feature> doInBackground(PackageInfo... params) {
List<Feature> features = new ArrayList<>();
PackageInfo app = params[0];
String packageName = getApplicationContext().getPackageName();
String metaDataKey = getString(R.string.category);
for (ActivityInfo info : app.activities) {
if (info.labelRes != 0 && info.name.startsWith(packageName)
&& !info.name.equals(FeatureOverviewActivity.class.getName())) {
String label = getString(info.labelRes);
String description = resolveString(info.descriptionRes);
String category = resolveMetaData(info.metaData, metaDataKey);
features.add(new Feature(info.name, label, description, category));
}
}
if (!features.isEmpty()) {
Comparator<Feature> comparator = (lhs, rhs) -> {
int result = lhs.getCategory().compareToIgnoreCase(rhs.getCategory());
if (result == 0) {
result = lhs.getLabel().compareToIgnoreCase(rhs.getLabel());
}
return result;
};
Collections.sort(features, comparator);
}
return features;
}
private String resolveMetaData(Bundle bundle, String key) {
String category = null;
if (bundle != null) {
category = bundle.getString(key);
}
return category;
}
private String resolveString(@StringRes int stringRes) {
try {
return getString(stringRes);
} catch (Resources.NotFoundException exception) {
return "-";
}
}
@Override
protected void onPostExecute(List<Feature> features) {
super.onPostExecute(features);
onFeaturesLoaded(features);
}
}
}

View File

@@ -0,0 +1,170 @@
package com.mapbox.mapboxsdk.testapp.activity
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Resources.NotFoundException
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener
import com.mapbox.mapboxsdk.testapp.R
import com.mapbox.mapboxsdk.testapp.adapter.FeatureAdapter
import com.mapbox.mapboxsdk.testapp.adapter.FeatureSectionAdapter
import com.mapbox.mapboxsdk.testapp.model.activity.Feature
import com.mapbox.mapboxsdk.testapp.utils.ItemClickSupport
import timber.log.Timber
import java.util.*
/**
* Activity shown when application is started
*
*
* This activity will generate data for RecyclerView based on the AndroidManifest entries.
* It uses tags as category and description to order the different entries.
*
*/
class FeatureOverviewActivity : AppCompatActivity() {
private var recyclerView: RecyclerView? = null
private var sectionAdapter: FeatureSectionAdapter? = null
private var features: List<Feature>? = null
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_feature_overview)
recyclerView = findViewById<View>(R.id.recyclerView) as RecyclerView
recyclerView!!.layoutManager = LinearLayoutManager(this)
recyclerView!!.addOnItemTouchListener(SimpleOnItemTouchListener())
recyclerView!!.setHasFixedSize(true)
ItemClickSupport.addTo(recyclerView)
.setOnItemClickListener { recyclerView: RecyclerView?, position: Int, view: View? ->
if (!sectionAdapter!!.isSectionHeaderPosition(position)) {
val itemPosition = sectionAdapter!!.getConvertedPosition(position)
val feature = features!![itemPosition]
startFeature(feature)
}
}
if (savedInstanceState == null) {
loadFeatures()
} else {
features = savedInstanceState.getParcelableArrayList(KEY_STATE_FEATURES)
onFeaturesLoaded(features)
}
}
private fun loadFeatures() {
try {
LoadFeatureTask().execute(
packageManager.getPackageInfo(
packageName,
PackageManager.GET_ACTIVITIES or PackageManager.GET_META_DATA
)
)
} catch (exception: PackageManager.NameNotFoundException) {
Timber.e(exception, "Could not resolve package info")
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun onFeaturesLoaded(featuresList: List<Feature>?) {
features = featuresList
if (featuresList == null || featuresList.isEmpty()) {
return
}
val sections: MutableList<FeatureSectionAdapter.Section> = ArrayList()
var currentCat = ""
for (i in features!!.indices) {
val category = features!![i].category
if (currentCat != category) {
sections.add(FeatureSectionAdapter.Section(i, category))
currentCat = category
}
}
sectionAdapter = FeatureSectionAdapter(
this,
R.layout.section_main_layout,
R.id.section_text,
FeatureAdapter(features!!)
)
sectionAdapter!!.setSections(sections.toTypedArray())
recyclerView!!.adapter = sectionAdapter
}
private fun startFeature(feature: Feature) {
val intent = Intent()
intent.component = ComponentName(packageName, feature.name)
startActivity(intent)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelableArrayList(KEY_STATE_FEATURES, features as ArrayList<Feature>?)
}
private inner class LoadFeatureTask : AsyncTask<PackageInfo?, Void?, List<Feature>>() {
protected override fun doInBackground(vararg p0: PackageInfo?): List<Feature>? {
val features: MutableList<Feature> = ArrayList()
val app = p0[0]
val packageName = applicationContext.packageName
val metaDataKey = getString(R.string.category)
if (app != null) {
for (info in app.activities) {
if (info.labelRes != 0 && info.name.startsWith(packageName) &&
info.name != FeatureOverviewActivity::class.java.name
) {
val label = getString(info.labelRes)
val description = resolveString(info.descriptionRes)
val category = resolveMetaData(info.metaData, metaDataKey)
features.add(Feature(info.name, label, description, category!!))
}
}
}
if (!features.isEmpty()) {
val comparator = Comparator { lhs: Feature, rhs: Feature ->
var result = lhs.category.compareTo(rhs.category, ignoreCase = true)
if (result == 0) {
result = lhs.getLabel().compareTo(rhs.getLabel(), ignoreCase = true)
}
result
}
Collections.sort(features, comparator)
}
return features
}
private fun resolveMetaData(bundle: Bundle?, key: String): String? {
var category: String? = null
if (bundle != null) {
category = bundle.getString(key)
}
return category
}
private fun resolveString(@StringRes stringRes: Int): String {
return try {
getString(stringRes)
} catch (exception: NotFoundException) {
"-"
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onPostExecute(features: List<Feature>) {
super.onPostExecute(features)
onFeaturesLoaded(features)
}
}
companion object {
private const val KEY_STATE_FEATURES = "featureList"
}
}

View File

@@ -1,16 +0,0 @@
package com.mapbox.mapboxsdk.testapp.utils;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity;
public class NavUtils {
public static void navigateHome(@NonNull Activity context) {
context.startActivity(new Intent(context, FeatureOverviewActivity.class));
context.finish();
}
}

View File

@@ -0,0 +1,12 @@
package com.mapbox.mapboxsdk.testapp.utils
import android.app.Activity
import android.content.Intent
import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity
object NavUtils {
fun navigateHome(context: Activity) {
context.startActivity(Intent(context, FeatureOverviewActivity::class.java))
context.finish()
}
}

View File

@@ -1,53 +0,0 @@
[
"BaseLocationActivity",
"MapSnapshotterMarkerActivity",
"MapSnapshotterReuseActivity",
"MapSnapshotterLocalStyleActivity",
"LatLngBoundsActivity",
"BottomSheetActivity",
"MockLocationEngine",
"DeleteRegionActivity",
"RealTimeGeoJsonActivity",
"UpdateMetadataActivity",
"CarDrivingActivity",
"MyLocationTrackingModeActivity",
"MyLocationToggleActivity",
"MyLocationTintActivity",
"MyLocationDrawableActivity",
"DoubleMapActivity",
"LocationPickerActivity",
"GeoJsonClusteringActivity",
"RuntimeStyleTestActivity",
"AnimatedSymbolLayerActivity",
"ViewPagerActivity",
"MapFragmentActivity",
"SupportMapFragmentActivity",
"SnapshotActivity",
"NavigationDrawerActivity",
"QueryRenderedFeaturesBoxHighlightActivity",
"MultiMapActivity",
"MapInDialogActivity",
"ManualZoomActivity",
"MaxMinZoomActivity",
"ScrollByActivity",
"ZoomFunctionSymbolLayerActivity",
"SymbolGeneratorActivity",
"TextureViewTransparentBackgroundActivity",
"SimpleMapActivity",
"RenderTestActivity",
"SymbolLayerActivity",
"LocationFragmentActivity",
"MergeOfflineRegionsActivity",
"NestedViewPagerActivity",
"DebugModeActivity",
"TextureViewDebugModeActivity",
"RuntimeStyleActivity",
"GridSourceActivity",
"AnimatedImageSourceActivity",
"EspressoTestActivity",
"ChangeResourcesCachePathActivity",
"EspressoTestActivity",
"FragmentBackStackActivity",
"ChildFragmentMapInDialogActivity",
"PerformanceMeasurementActivity"
]

View File

@@ -1,73 +0,0 @@
'use strict';
const fs = require('fs');
const ejs = require('ejs');
global.iff = function (condition, val) {
return condition() ? val : "";
}
global.camelize = function (str) {
return str.replace(/(?:^|-)(.)/g, function (_, x) {
return x.toUpperCase();
});
}
const excludeClasses = JSON.parse(fs.readFileSync('scripts/exclude-activity-gen.json', 'utf8'));
const appBasePath = 'MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity';
const testBasePath = 'MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/gen';
const subPackages = fs.readdirSync(appBasePath);
const ejsConversionTask = ejs.compile(fs.readFileSync('MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/activity.junit.ejs', 'utf8'), {strict: true});
if (!fs.existsSync(testBasePath)){
fs.mkdirSync(testBasePath);
}
console.log("\nGenerating test activities:\n");
var generatedClasses = [];
var excludedClasses = [];
for(const subPackage of subPackages) {
if(!(subPackage.slice(-5) === '.java')) {
const activities = fs.readdirSync(`${appBasePath}/${subPackage}`);
// create directories for package
if (!fs.existsSync(`${testBasePath}/${subPackage}`)){
fs.mkdirSync(`${testBasePath}/${subPackage}`);
}
for (const activity of activities) {
var activityName;
if (activity.slice(-5) === '.java') {
// .java file
activityName = activity.slice(0, -5);
} else {
// .java file
activityName = activity.slice(0, -3);
}
// create path for test file
const filePath = `${testBasePath}/${subPackage}/${activityName}Test.kt`;
// try removing previous generated files
try {
fs.accessSync(filePath, fs.F_OK);
fs.unlinkSync(filePath);
} catch (e) {
}
// only generate test file if not part of exclude list + if contains Activity in name
if ((!(excludeClasses.indexOf(activityName) > -1)) && activityName.includes("Activity")) {
fs.writeFileSync(filePath, ejsConversionTask([activityName, subPackage]));
generatedClasses.push(activityName);
}else{
excludedClasses.push(activityName);
}
}
}
}
for(const generatedClass of generatedClasses){
console.log(`${generatedClass}Test`);
}
console.log(`\nFinished generating ${generatedClasses.length} activity sanity tests, excluded ${excludeClasses.length} classes.\n`);