Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: automatically import Android plugins #3788

Merged
merged 48 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
afb21ad
feat(cli): STUDIO_PATH environment variable
imhoffd Nov 2, 2020
e1e7f66
check before opening
imhoffd Nov 2, 2020
cf26a4b
use config for config file name
imhoffd Nov 2, 2020
c96aa18
split out extConfig stuff
imhoffd Nov 2, 2020
d541287
load js
imhoffd Nov 2, 2020
7d0e5c1
copy = write json from loaded config
imhoffd Nov 2, 2020
8a47e91
init for non-json
imhoffd Nov 3, 2020
5e98605
Merge branch 'main' into dynamic-config
imhoffd Nov 3, 2020
76264f2
revert the help output
imhoffd Nov 3, 2020
9462e6a
support TS files
imhoffd Nov 3, 2020
859c0ef
expose CapacitorConfig type
imhoffd Nov 3, 2020
c2eb9c3
internalize readonly
imhoffd Nov 3, 2020
7861b80
document all the things
imhoffd Nov 3, 2020
4b3b2bd
undo core change
imhoffd Nov 3, 2020
e6450aa
add definitions for plugin config values
imhoffd Nov 4, 2020
de6e9e1
locate plugins by allowlist
imhoffd Nov 4, 2020
3e8db78
add warning for deprecated usage
imhoffd Nov 6, 2020
c2075bd
Merge branch 'main' into plugin-locator
imhoffd Nov 9, 2020
a56d83d
Merge branch 'main' into plugin-locator
imhoffd Nov 10, 2020
7a83d03
write capacitor.plugins.json
imhoffd Nov 10, 2020
9944b81
load from json
imhoffd Nov 10, 2020
dd74cdb
Merge branch 'main' into auto-android
imhoffd Nov 25, 2020
917a739
Merge branch 'main' into auto-android
imhoffd Nov 25, 2020
d0ac650
no need to override onCreate
imhoffd Nov 25, 2020
27942d6
remove declarations
imhoffd Nov 25, 2020
633f5a7
refactor(android): consolidate bridge construction
imhoffd Nov 25, 2020
9f5068b
bridge activity changes
imhoffd Nov 29, 2020
6e94cbb
Merge branch 'main' into auto-android
imhoffd Nov 29, 2020
9ee8794
add plugins shortcut
imhoffd Nov 29, 2020
b0e2e4f
take out init deprecations
imhoffd Nov 29, 2020
c805e40
revert viz change
imhoffd Nov 29, 2020
e05bfd9
fix call
imhoffd Nov 29, 2020
abdc85a
Merge branch 'main' into refactor-bridge
imhoffd Nov 29, 2020
6c7c0c3
Merge branch 'refactor-bridge' into auto-android
imhoffd Nov 29, 2020
979fbe5
deprecate and load automatically
imhoffd Nov 29, 2020
25d7726
make it work with NativePlugin annotation
imhoffd Nov 30, 2020
fcd18a0
plugin manager, error handling
imhoffd Nov 30, 2020
77807c9
integrate Builder
imhoffd Nov 30, 2020
4177c42
Merge branch 'main' into refactor-bridge
imhoffd Nov 30, 2020
ae00035
Merge branch 'refactor-bridge' into auto-android
imhoffd Nov 30, 2020
b17fa0a
build() -> create()
imhoffd Nov 30, 2020
1d6d4b7
Merge branch 'refactor-bridge' into auto-android
imhoffd Dec 1, 2020
1eb89dd
Merge branch 'main' into auto-android
imhoffd Dec 1, 2020
9407e06
add deprecation messages
imhoffd Dec 1, 2020
2070055
Update android/capacitor/src/main/java/com/getcapacitor/PluginManager…
imhoffd Dec 1, 2020
d06e23f
add registerPlugin/registerPlugins helpers
imhoffd Dec 1, 2020
c6164e0
Merge branch 'main' into auto-android
imhoffd Dec 1, 2020
35d1bd8
Merge branch 'main' into auto-android
imhoffd Dec 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,46 @@ public class BridgeActivity extends AppCompatActivity {
private JSONObject config;
private int activityDepth = 0;
private List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
protected Bridge.Builder bridgeBuilder = new Bridge.Builder();
private final Bridge.Builder bridgeBuilder = new Bridge.Builder();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bridgeBuilder.setInstanceState(savedInstanceState);
}

/**
* @deprecated It is preferred not to call this method. If it is not called, the bridge is
* initialized automatically. If you need to add additional plugins during initialization,
* use {@link BridgeActivity#bridgeBuilder}.
*/
@Deprecated
protected void init(Bundle savedInstanceState, List<Class<? extends Plugin>> plugins) {
this.init(savedInstanceState, plugins, null);
}

/**
* @deprecated It is preferred not to call this method. If it is not called, the bridge is
* initialized automatically. If you need to add additional plugins during initialization,
* use {@link BridgeActivity#bridgeBuilder}.
*/
@Deprecated
protected void init(Bundle savedInstanceState, List<Class<? extends Plugin>> plugins, JSONObject config) {
this.initialPlugins = plugins;
this.config = config;

this.load(savedInstanceState);
this.load();
}

/**
* Load the WebView and create the Bridge
* @deprecated This method should not be called manually.
*/
@Deprecated
protected void load(Bundle savedInstanceState) {
this.load();
}

private void load() {
getApplication().setTheme(getResources().getIdentifier("AppTheme_NoActionBar", "style", getPackageName()));
setTheme(getResources().getIdentifier("AppTheme_NoActionBar", "style", getPackageName()));
setTheme(R.style.AppTheme_NoActionBar);
Expand All @@ -51,6 +68,14 @@ protected void load(Bundle savedInstanceState) {
this.onNewIntent(getIntent());
}

public void registerPlugin(Class<? extends Plugin> plugin) {
bridgeBuilder.addPlugin(plugin);
}

public void registerPlugins(List<Class<? extends Plugin>> plugins) {
bridgeBuilder.addPlugins(plugins);
}

public Bridge getBridge() {
return this.bridge;
}
Expand All @@ -64,6 +89,20 @@ public void onSaveInstanceState(Bundle outState) {
@Override
public void onStart() {
super.onStart();

// Preferred behavior: init() was not called, so we construct the bridge with auto-loaded plugins.
if (bridge == null) {
PluginManager loader = new PluginManager(getAssets());

try {
bridgeBuilder.addPlugins(loader.loadPluginClasses());
} catch (PluginLoadException ex) {
Logger.error("Error loading plugins.", ex);
}

this.load();
}

activityDepth++;
this.bridge.onStart();
Logger.debug("App started");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.getcapacitor;

import android.content.res.AssetManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class PluginManager {

private final AssetManager assetManager;

public PluginManager(AssetManager assetManager) {
this.assetManager = assetManager;
}

public List<Class<? extends Plugin>> loadPluginClasses() throws PluginLoadException {
JSONArray pluginsJSON = parsePluginsJSON();
ArrayList<Class<? extends Plugin>> pluginList = new ArrayList<>();

try {
for (int i = 0, size = pluginsJSON.length(); i < size; i++) {
JSONObject pluginJSON = pluginsJSON.getJSONObject(i);
String classPath = pluginJSON.getString("classpath");
Class<?> c = Class.forName(classPath);
pluginList.add(c.asSubclass(Plugin.class));
}
} catch (JSONException e) {
throw new PluginLoadException("Could not parse capacitor.plugins.json as JSON");
} catch (ClassNotFoundException e) {
throw new PluginLoadException("Could not find class by class path: " + e.getMessage());
}

return pluginList;
}

private JSONArray parsePluginsJSON() throws PluginLoadException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open("capacitor.plugins.json")))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
String jsonString = builder.toString();
return new JSONArray(jsonString);
} catch (IOException e) {
throw new PluginLoadException("Could not load capacitor.plugins.json");
} catch (JSONException e) {
throw new PluginLoadException("Could not parse capacitor.plugins.json as JSON");
}
}
}
108 changes: 106 additions & 2 deletions cli/src/android/update.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { copy, remove, pathExists, readFile, writeFile } from '@ionic/utils-fs';
import { dirname, join, relative, resolve } from 'path';
import {
copy,
remove,
pathExists,
readdirp,
readFile,
writeFile,
writeJSON,
} from '@ionic/utils-fs';
import Debug from 'debug';
import { dirname, extname, join, relative, resolve } from 'path';

import c from '../colors';
import { checkPlatformVersions, runTask } from '../common';
Expand Down Expand Up @@ -27,6 +36,7 @@ import { resolveNode } from '../util/node';
import { getAndroidPlugins } from './common';

const platform = 'android';
const debug = Debug('capacitor:android:update');

export async function updateAndroid(config: Config): Promise<void> {
const plugins = await getPluginsTask(config);
Expand All @@ -37,6 +47,7 @@ export async function updateAndroid(config: Config): Promise<void> {

printPlugins(capacitorPlugins, 'android');

await writePluginsJson(config, capacitorPlugins);
await removePluginsNativeFiles(config);
const cordovaPlugins = plugins.filter(
p => getPluginType(p, platform) === PluginType.Cordova,
Expand All @@ -61,6 +72,99 @@ function getGradlePackageName(id: string): string {
return id.replace('@', '').replace('/', '-');
}

interface PluginsJsonEntry {
pkg: string;
classpath: string;
}

async function writePluginsJson(
config: Config,
plugins: Plugin[],
): Promise<void> {
const classes = await findAndroidPluginClasses(plugins);
const pluginsJsonPath = resolve(
config.android.assetsDirAbs,
'capacitor.plugins.json',
);

await writeJSON(pluginsJsonPath, classes, { spaces: '\t' });
}

async function findAndroidPluginClasses(
plugins: Plugin[],
): Promise<PluginsJsonEntry[]> {
const entries: PluginsJsonEntry[] = [];

for (const plugin of plugins) {
entries.push(...(await findAndroidPluginClassesInPlugin(plugin)));
}

return entries;
}

async function findAndroidPluginClassesInPlugin(
plugin: Plugin,
): Promise<PluginsJsonEntry[]> {
if (!plugin.android || getPluginType(plugin, platform) !== PluginType.Core) {
return [];
}

const srcPath = resolve(plugin.rootPath, plugin.android.path, 'src/main');
const srcFiles = await readdirp(srcPath, {
filter: entry =>
!entry.stats.isDirectory() &&
['.java', '.kt'].includes(extname(entry.path)),
});

const classRegex = /^@(?:CapacitorPlugin|NativePlugin)[\s\S]+?class ([\w]+)/gm;
const packageRegex = /^package ([\w.]+);?$/gm;

debug(
'Searching %O source files in %O by %O regex',
srcFiles.length,
srcPath,
classRegex,
);

const entries = await Promise.all(
srcFiles.map(
async (srcFile): Promise<PluginsJsonEntry | undefined> => {
const srcFileContents = await readFile(srcFile, { encoding: 'utf-8' });
const classMatch = classRegex.exec(srcFileContents);

if (classMatch) {
const className = classMatch[1];

debug('Searching %O for package by %O regex', srcFile, packageRegex);

const packageMatch = packageRegex.exec(
srcFileContents.substring(0, classMatch.index),
);

if (!packageMatch) {
logFatal(
`Package could not be parsed from Android plugin.\n` +
`Location: ${c.strong(srcFile)}`,
);
}

const packageName = packageMatch[1];
const classpath = `${packageName}.${className}`;

debug('%O is a suitable plugin class', classpath);

return {
pkg: plugin.id,
classpath,
};
}
},
),
);

return entries.filter((entry): entry is PluginsJsonEntry => !!entry);
}

export async function installGradlePlugins(
config: Config,
capacitorPlugins: Plugin[],
Expand Down