Unverified Commit 7ee105df authored by Evan W. Patton's avatar Evan W. Patton Committed by Jeffrey I. Schiller

Implement additional companion build targets

We now need to build three different companions for every
release. This change makes it so that we can build all three with a
single run of ant rather than having to configure multiple different
companions and performing three separate builds of App Inventor for a
components release.

Change-Id: Ia0ed7e85513081e1fbaff606223f7d88b4dbc638
parent 5868875b
{
"categoryString": "EXTENSION",
"dateBuilt": "2019-05-02T17:02:15-0400",
"nonVisible": "true",
"iconName": "",
"methods": [{
"deprecated": "false",
"name": "Extra1",
"description": "Extra1 -- Extra function to download and install an APK file.",
"params": [{
"name": "urlToApk",
"type": "text"
}]
}],
"blockProperties": [],
"helpUrl": "",
"type": "edu.mit.appinventor.companionextras.CompanionExtras",
"androidMinSdk": 7,
"version": "1",
"external": "true",
"showOnPalette": "true",
"name": "CompanionExtras",
"helpString": "An extension to provide additional functionality to non-Play Store Companions",
"properties": [],
"events": []
}
\ No newline at end of file
[{
"categoryString": "EXTENSION",
"dateBuilt": "2019-05-02T17:02:15-0400",
"nonVisible": "true",
"iconName": "",
"methods": [{
"deprecated": "false",
"name": "Extra1",
"description": "Extra1 -- Extra function to download and install an APK file.",
"params": [{
"name": "urlToApk",
"type": "text"
}]
}],
"blockProperties": [],
"helpUrl": "",
"type": "edu.mit.appinventor.companionextras.CompanionExtras",
"androidMinSdk": 7,
"version": "1",
"external": "true",
"showOnPalette": "true",
"name": "CompanionExtras",
"helpString": "An extension to provide additional functionality to non-Play Store Companions",
"properties": [],
"events": []
}]
\ No newline at end of file
{"assets":[],"native":[],"permissions":["android.permission.REQUEST_INSTALL_PACKAGES"],"activities":[],"broadcastReceivers":[],"broadcastReceiver":[],"libraries":[],"type":"edu.mit.appinventor.companionextras.CompanionExtras","androidMinSdk":["7"]}
\ No newline at end of file
[{"assets":[],"native":[],"permissions":["android.permission.REQUEST_INSTALL_PACKAGES"],"activities":[],"broadcastReceivers":[],"broadcastReceiver":[],"libraries":[],"type":"edu.mit.appinventor.companionextras.CompanionExtras","androidMinSdk":["7"]}]
\ No newline at end of file
......@@ -19,6 +19,8 @@
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver"/>
<ant inheritAll="false" useNativeBasedir="true" dir="components"/>
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayApp"/>
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayAppExtras"/>
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="Emulator"/>
</target>
<target name="MakeAuthKey">
......@@ -61,6 +63,14 @@
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayApp"/>
</target>
<target name="PlayAppExtras">
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayAppExtras"/>
</target>
<target name="Emulator">
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="Emulator"/>
</target>
<target name="AIMergerApp">
<ant inheritAll="false" useNativeBasedir="true" dir="aimerger" target="AIMergerApp"/>
</target>
......
......@@ -202,14 +202,22 @@
validity="10000" />
</target>
<target name="CheckPlayAppSrcZip">
<uptodate property="PlayAppSrcZip.uptodate" targetfile="${local.build.dir}/aiplayapp.zip">
<srcfiles file="${user.home}/.appinventor/debug.keystore" />
<srcfiles dir="${appinventor.dir}/aiplayapp" includes="src/**/*,youngandroidproject/*,assets/**/*" />
</uptodate>
</target>
<!-- =====================================================================
GenPlayAppSrcZip Version for the Google Play Store works over WiFi
===================================================================== -->
<target name="GenPlayAppSrcZip"
depends="init,MakeKeyStore">
depends="init,MakeKeyStore,CheckPlayAppSrcZip"
unless="PlayAppSrcZip.uptodate">
<copy tofile="${appinventor.dir}/aiplayapp/android.keystore" file="${user.home}/.appinventor/debug.keystore" />
<zip destfile="${local.build.dir}/aiplayapp.zip" basedir="${appinventor.dir}/aiplayapp" filesonly="true"
includes="src/**/*,youngandroidproject/*,assets/*,android.keystore" />
includes="src/**/*,youngandroidproject/*,assets/**/*,android.keystore" />
<!-- We delete the keyfile below so as to not leave it laying around the source
tree -->
<delete file="${appinventor.dir}/aiplayapp/android.keystore" failonerror="true" />
......@@ -254,6 +262,79 @@
</java>
</target>
<target name="CheckPlayAppExtras">
<uptodate property="PlayAppExtras.uptodate" targetfile="${public.build.dir}/MITAI2Companion-full.apk">
<srcfiles file="${local.build.dir}/aiplayapp.zip"/>
<srcfiles dir="${run.lib.dir}" includes="*.jar"/>
</uptodate>
</target>
<!-- =====================================================================
PlayAppExtras: builds ../build/buildserver/MITAI2Companion-full.apk
===================================================================== -->
<target name="PlayAppExtras"
depends="CheckPlayAppExtras"
unless="PlayAppExtras.uptodate">
<java classname="com.google.appinventor.buildserver.Main" fork="true" failonerror="true">
<classpath>
<fileset dir="${run.lib.dir}" includes="*.jar" />
</classpath>
<sysproperty key="file.encoding" value="UTF-8" />
<arg value="--childProcessRamMb" />
<arg value="1024" />
<arg value="--inputZipFile" />
<arg value="${local.build.dir}/aiplayapp.zip" />
<arg value="--userName" />
<arg value="App Inventor" />
<arg value="--isForCompanion" />
<arg value="--includeDangerousPermissions" />
<arg value="--extensions" />
<arg value="edu.mit.appinventor.companionextras.CompanionExtras" />
<arg value="--outputDir" />
<arg value="${public.build.dir}" />
<arg value="--outputFileName" />
<arg value="MITAI2Companion-full.apk" />
<arg value="--dexCacheDir" />
<arg value="${public.build.dir}/dexCache" />
</java>
</target>
<target name="CheckEmulator">
<uptodate property="Emulator.uptodate" targetfile="${public.build.dir}/Emulator.apk">
<srcfiles file="${local.build.dir}/aiplayapp.zip"/>
<srcfiles dir="${run.lib.dir}" includes="*.jar"/>
</uptodate>
</target>
<!-- =====================================================================
Emulator: builds ../build/buildserver/Emulator.apk
===================================================================== -->
<target name="Emulator"
depends="CheckEmulator"
unless="Emulator.uptodate">
<java classname="com.google.appinventor.buildserver.Main" fork="true" failonerror="true">
<classpath>
<fileset dir="${run.lib.dir}" includes="*.jar" />
</classpath>
<sysproperty key="file.encoding" value="UTF-8" />
<arg value="--childProcessRamMb" />
<arg value="1024" />
<arg value="--inputZipFile" />
<arg value="${local.build.dir}/aiplayapp.zip" />
<arg value="--userName" />
<arg value="App Inventor" />
<arg value="--isForCompanion" />
<arg value="--isForEmulator" />
<arg value="--includeDangerousPermissions" />
<arg value="--outputDir" />
<arg value="${public.build.dir}" />
<arg value="--outputFileName" />
<arg value="Emulator.apk" />
<arg value="--dexCacheDir" />
<arg value="${public.build.dir}/dexCache" />
</java>
</target>
<!-- =====================================================================
Install the Companion on a connected phone.
===================================================================== -->
......
......@@ -630,8 +630,9 @@ public class BuildServer {
// actually be deleted. That's only if the build server is killed (via ctrl+c) while a build
// is happening, so we should be careful about that.
outputDir.deleteOnExit();
Result buildResult = projectBuilder.build(userName, new ZipFile(zipFile), outputDir, false,
commandLineOptions.childProcessRamMb, commandLineOptions.dexCacheDir, reporter);
Result buildResult = projectBuilder.build(userName, new ZipFile(zipFile), outputDir, null,
false, false, false, null,
commandLineOptions.childProcessRamMb, commandLineOptions.dexCacheDir, reporter);
String buildOutput = buildResult.getOutput();
LOG.info("Build output: " + buildOutput);
String buildError = buildResult.getError();
......
......@@ -230,6 +230,8 @@ public final class Compiler {
private final int childProcessRamMb; // Maximum ram that can be used by a child processes, in MB.
private final boolean isForCompanion;
private final boolean isForEmulator;
private final boolean includeDangerousPermissions;
private final Project project;
private final PrintStream out;
private final PrintStream err;
......@@ -401,6 +403,9 @@ public final class Compiler {
*/
@VisibleForTesting
void generateNativeLibNames() {
if (isForEmulator) { // no libraries for emulator
return;
}
try {
loadJsonInfo(nativeLibsNeeded, ComponentDescriptorConstants.NATIVE_TARGET);
} catch (IOException e) {
......@@ -722,6 +727,9 @@ public final class Compiler {
String projectName = project.getProjectName();
String vCode = (project.getVCode() == null) ? DEFAULT_VERSION_CODE : project.getVCode();
String vName = (project.getVName() == null) ? DEFAULT_VERSION_NAME : cleanName(project.getVName());
if (includeDangerousPermissions) {
vName += "u";
}
String aName = (project.getAName() == null) ? DEFAULT_APP_NAME : cleanName(project.getAName());
LOG.log(Level.INFO, "VCode: " + project.getVCode());
LOG.log(Level.INFO, "VName: " + project.getVName());
......@@ -779,10 +787,11 @@ public final class Compiler {
// Remove Google's Forbidden Permissions
// This code is crude because we had to do this on short notice
if (isForCompanion && AppInventorFeatures.limitPermissions()) {
if (isForCompanion && !includeDangerousPermissions) {
permissions.remove("android.permission.RECEIVE_SMS");
permissions.remove("android.permission.SEND_SMS");
permissions.remove("android.permission.PROCESS_OUTGOING_CALLS");
permissions.remove("android.permission.CALL_PHONE");
}
for (String permission : permissions) {
......@@ -791,7 +800,6 @@ public final class Compiler {
if (isForCompanion) { // This is so ACRA can do a logcat on phones older then Jelly Bean
out.write(" <uses-permission android:name=\"android.permission.READ_LOGS\" />\n");
out.write(" <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n");
}
// TODO(markf): Change the minSdkVersion below if we ever require an SDK beyond 1.5.
......@@ -903,6 +911,10 @@ public final class Compiler {
for (Map.Entry<String, Set<String>> componentSubElSetPair : subelements) {
Set<String> subelementSet = componentSubElSetPair.getValue();
for (String subelement : subelementSet) {
if (isForCompanion && !includeDangerousPermissions &&
subelement.contains("android.provider.Telephony.SMS_RECEIVED")) {
continue;
}
out.write(subelement);
}
}
......@@ -925,6 +937,17 @@ public final class Compiler {
for (String broadcastReceiver : simpleBroadcastReceivers) {
String[] brNameAndActions = broadcastReceiver.split(",");
if (brNameAndActions.length == 0) continue;
// Remove the SMS_RECEIVED broadcast receiver if we aren't including dangerous permissions
if (isForCompanion && !includeDangerousPermissions) {
boolean skip = false;
for (String action : brNameAndActions) {
if (action.equalsIgnoreCase("android.provider.Telephony.SMS_RECEIVED")) {
skip = true;
break;
}
}
if (skip) continue;
}
out.write(
"<receiver android:name=\"" + brNameAndActions[0] + "\" >\n");
if (brNameAndActions.length > 1){
......@@ -979,14 +1002,16 @@ public final class Compiler {
*/
public static boolean compile(Project project, Set<String> compTypes, Map<String, Set<String>> compBlocks,
PrintStream out, PrintStream err, PrintStream userErrors,
boolean isForCompanion, String keystoreFilePath,
int childProcessRam, String dexCacheDir,
boolean isForCompanion, boolean isForEmulator,
boolean includeDangerousPermissions, String keystoreFilePath,
int childProcessRam, String dexCacheDir, String outputFileName,
BuildServer.ProgressReporter reporter) throws IOException, JSONException {
long start = System.currentTimeMillis();
// Create a new compiler instance for the compilation
Compiler compiler = new Compiler(project, compTypes, compBlocks, out, err, userErrors, isForCompanion,
childProcessRam, dexCacheDir, reporter);
Compiler compiler = new Compiler(project, compTypes, compBlocks, out, err, userErrors,
isForCompanion, isForEmulator, includeDangerousPermissions, childProcessRam, dexCacheDir,
reporter);
compiler.generateAssets();
compiler.generateActivities();
......@@ -1125,8 +1150,11 @@ public final class Compiler {
// Seal the apk with ApkBuilder
out.println("________Invoking ApkBuilder");
String apkAbsolutePath = deployDir.getAbsolutePath() + SLASH +
project.getProjectName() + ".apk";
String fileName = outputFileName;
if (fileName == null) {
fileName = project.getProjectName() + ".apk";
}
String apkAbsolutePath = deployDir.getAbsolutePath() + SLASH + fileName;
if (!compiler.runApkBuilder(apkAbsolutePath, tmpPackageName, dexedClassesDir)) {
return false;
}
......@@ -1245,7 +1273,7 @@ public final class Compiler {
*/
@VisibleForTesting
Compiler(Project project, Set<String> compTypes, Map<String, Set<String>> compBlocks, PrintStream out, PrintStream err,
PrintStream userErrors, boolean isForCompanion,
PrintStream userErrors, boolean isForCompanion, boolean isForEmulator, boolean includeDangerousPermissions,
int childProcessMaxRam, String dexCacheDir, BuildServer.ProgressReporter reporter) {
this.project = project;
this.compBlocks = compBlocks;
......@@ -1257,6 +1285,8 @@ public final class Compiler {
this.err = err;
this.userErrors = userErrors;
this.isForCompanion = isForCompanion;
this.isForEmulator = isForEmulator;
this.includeDangerousPermissions = includeDangerousPermissions;
this.childProcessRamMb = childProcessMaxRam;
this.dexCacheDir = dexCacheDir;
this.reporter = reporter;
......
......@@ -9,6 +9,7 @@ package com.google.appinventor.buildserver;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.spi.StringArrayOptionHandler;
import java.io.File;
import java.io.IOException;
......@@ -45,6 +46,23 @@ public final class Main {
@Option(name = "--dexCacheDir",
usage = "the directory to cache the pre-dexed libraries")
String dexCacheDir = null;
@Option(name = "--includeDangerousPermissions",
usage = "Add extra features not allowed in the Google Play store.")
boolean includeDangerousPermissions = false;
@Option(name = "--extensions",
usage = "Include the named extensions in the compilation.",
handler = StringArrayOptionHandler.class)
String[] extensions = null;
@Option(name = "--outputFileName",
usage = "Use the specified file name for output rather than the App Name.")
String outputFileName = null;
@Option(name = "--isForEmulator",
usage = "Exclude native libraries for emulator.")
boolean isForEmulator = false;
}
private static CommandLineOptions commandLineOptions = new CommandLineOptions();
......@@ -82,7 +100,11 @@ public final class Main {
Result result = projectBuilder.build(commandLineOptions.userName,
zip,
commandLineOptions.outputDir,
commandLineOptions.outputFileName,
commandLineOptions.isForCompanion,
commandLineOptions.isForEmulator,
commandLineOptions.includeDangerousPermissions,
commandLineOptions.extensions,
commandLineOptions.childProcessRamMb,
commandLineOptions.dexCacheDir, null);
System.exit(result.getResult());
......
......@@ -28,8 +28,11 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -116,7 +119,8 @@ public final class ProjectBuilder {
+ baseNamePrefix + "0 to " + baseNamePrefix + (TEMP_DIR_ATTEMPTS - 1) + ')');
}
Result build(String userName, ZipFile inputZip, File outputDir, boolean isForCompanion,
Result build(String userName, ZipFile inputZip, File outputDir, String outputFileName,
boolean isForCompanion, boolean isForEmulator, boolean includeDangerousPermissions, String[] extraExtensions,
int childProcessRam, String dexCachePath, BuildServer.ProgressReporter reporter) {
try {
// Download project files into a temporary directory
......@@ -150,14 +154,21 @@ public final class ProjectBuilder {
ByteArrayOutputStream errors = new ByteArrayOutputStream();
PrintStream userErrors = new PrintStream(errors);
Set<String> componentTypes = isForCompanion ? getAllComponentTypes() :
getComponentTypes(sourceFiles, project.getAssetsDirectory());
Set<String> componentTypes = getComponentTypes(sourceFiles, project.getAssetsDirectory());
if (isForCompanion) {
componentTypes.addAll(getAllComponentTypes());
}
if (extraExtensions != null) {
System.err.println("Including extension: " + Arrays.toString(extraExtensions));
Collections.addAll(componentTypes, extraExtensions);
}
Map<String, Set<String>> componentBlocks = getComponentBlocks(sourceFiles);
// Invoke YoungAndroid compiler
boolean success =
Compiler.compile(project, componentTypes, componentBlocks, console, console, userErrors,
isForCompanion, keyStorePath, childProcessRam, dexCachePath, reporter);
isForCompanion, isForEmulator, includeDangerousPermissions, keyStorePath,
childProcessRam, dexCachePath, outputFileName, reporter);
console.close();
userErrors.close();
......@@ -168,8 +179,12 @@ public final class ProjectBuilder {
if (success) {
// Locate output file
String fileName = outputFileName;
if (fileName == null) {
fileName = project.getProjectName() + ".apk";
}
File outputFile = new File(projectRoot,
"build/deploy/" + project.getProjectName() + ".apk");
"build/deploy/" + fileName);
if (!outputFile.exists()) {
LOG.warning("Young Android build - " + outputFile + " does not exist");
} else {
......
......@@ -24,14 +24,14 @@ public class CompilerTest extends TestCase {
Set<String> noComponents = Sets.newHashSet();
Map<String, Set<String>> noComponentBlocks = Maps.newHashMap();
Compiler compiler = new Compiler(null, noComponents, noComponentBlocks, System.out, System.err, System.err, false,
2048, null, null);
false, false, 2048, null, null);
compiler.generatePermissions();
Map<String,Set<String>> permissions = compiler.getPermissions();
assertEquals(0, permissions.size());
Set<String> componentTypes = Sets.newHashSet("com.google.appinventor.components.runtime.LocationSensor");
compiler = new Compiler(null, componentTypes, noComponentBlocks, System.out, System.err, System.err, false, 2048, null, null);
compiler = new Compiler(null, componentTypes, noComponentBlocks, System.out, System.err, System.err, false, false, false, 2048, null, null);
compiler.generatePermissions();
permissions = compiler.getPermissions();
Set<String> flatPermissions = Sets.newHashSet();
......@@ -57,7 +57,7 @@ public class CompilerTest extends TestCase {
Set<String> componentTypes = Sets.newHashSet(texting);
Map<String, Set<String>> blocks = Maps.newHashMap();
blocks.put("Texting", Sets.newHashSet("ReceivingEnabled", "GoogleVoiceEnabled"));
Compiler compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, 2048, null, null);
Compiler compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, false, false, 2048, null, null);
compiler.generateBroadcastReceivers();
Map<String, Set<String>> componentReceivers = compiler.getBroadcastReceivers();
Set<String> receivers = Sets.newHashSet();
......@@ -75,7 +75,7 @@ public class CompilerTest extends TestCase {
assertTrue(hasGoogleVoice);
componentTypes = Sets.newHashSet(texting, label);
compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, 2048, null, null);
compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, false, false, 2048, null, null);
compiler.generateBroadcastReceivers();
componentReceivers = compiler.getBroadcastReceivers();
receivers.clear();
......@@ -93,7 +93,7 @@ public class CompilerTest extends TestCase {
Set<String> componentTypes = Sets.newHashSet(barcodeScanner);
Map<String, Set<String>> blocks = Maps.newHashMap();
Compiler compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, 2048, null, null);
Compiler compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, false, false, 2048, null, null);
compiler.generateActivities();
Map<String, Set<String>> componentActivities = compiler.getActivities();
Set<String> activities = componentActivities.get(barcodeScanner);
......@@ -107,7 +107,7 @@ public class CompilerTest extends TestCase {
assertTrue(activityElementString.contains("windowSoftInputMode=\"stateAlwaysHidden\""));
componentTypes = Sets.newHashSet(listPicker);
compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, 2048, null, null);
compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, false, false, 2048, null, null);
compiler.generateActivities();
componentActivities = compiler.getActivities();
activities = componentActivities.get(listPicker);
......@@ -118,7 +118,7 @@ public class CompilerTest extends TestCase {
assertTrue(activityElementString.contains("screenOrientation=\"behind\""));
componentTypes = Sets.newHashSet(twitter);
compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, 2048, null, null);
compiler = new Compiler(null, componentTypes, blocks, System.out, System.err, System.err, false, false, false, 2048, null, null);
compiler.generateActivities();
componentActivities = compiler.getActivities();
activities = componentActivities.get(twitter);
......
......@@ -124,16 +124,4 @@ public final class AppInventorFeatures {
return false;
}
/** limitPermissions -- Remove Google defined "dangerous" permissions when
* building the MIT AI2 Companion. These include:
* android.permission.RECEIVE_SMS
* android.permission.SEND_SMS
* android.permission.PROCESS_OUTGOING_CALLS
*
* @return true to limit permissions in the Companion
*/
public static boolean limitPermissions() {
return true;
}
}
......@@ -43,6 +43,8 @@ import com.google.appinventor.components.runtime.util.EclairUtil;
import com.google.appinventor.components.runtime.util.SdkLevel;
import com.google.appinventor.components.runtime.util.WebRTCNativeMgr;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.Formatter;
......@@ -240,9 +242,17 @@ public class PhoneStatus extends AndroidNonvisibleComponent implements Component
@SimpleFunction(description = "Downloads the URL and installs it as an Android Package via the installed browser")
public void installURL(String url) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW).setData(uri);
form.startActivity(intent);
try {
Class<?> clazz = Class.forName("edu.mit.appinventor.companionextras.CompanionExtras");
Object o = clazz.getConstructor(Form.class).newInstance(form);
Method m = clazz.getMethod("Extra1", String.class);
m.invoke(o, url);
} catch (Exception e) {
// Fall back to using the browser
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW).setData(uri);
form.startActivity(intent);
}
}
@SimpleFunction(description = "Really Exit the Application")
......
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright © 2019 MIT, All rights reserved.
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package edu.mit.appinventor.companionextras;
import android.Manifest;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.Form;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.NougatUtil;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
@UsesPermissions(permissionNames = Manifest.permission.REQUEST_INSTALL_PACKAGES)
@DesignerComponent(version = 1,
category = ComponentCategory.EXTENSION,
description = "An extension to provide additional functionality to non-Play Store Companions",
nonVisible = true)
@SimpleObject(external = true)
public class CompanionExtras extends AndroidNonvisibleComponent {
private static final String LOG_TAG = CompanionExtras.class.getSimpleName();
private static final String REPL_ASSET_DIR =
Environment.getExternalStorageDirectory().getAbsolutePath() +
"/AppInventor/assets/";
public CompanionExtras(Form form) {
super(form);
}
/**
* Extra1 -- Extra function to download and install an APK file.
*
* @param urlToApk url pointing to the APK to download and install
*/
@SimpleFunction(description = "")
public void Extra1(final String urlToApk) {
AsynchUtil.runAsynchronously(new Runnable() {
@Override
public void run() {
Uri packageuri = null;
try {
URL url = new URL(urlToApk);
URLConnection conn = url.openConnection();
File rootDir = new File(REPL_ASSET_DIR);
InputStream instream = new BufferedInputStream(conn.getInputStream());
File apkfile = new File(rootDir + "/package.apk");
FileOutputStream apkOut = new FileOutputStream(apkfile);
byte[] buffer = new byte[32768];
int len;
while ((len = instream.read(buffer, 0, 32768)) > 0) {
apkOut.write(buffer, 0, len);
}
instream.close();
apkOut.close();
// Call Package Manager Here
Log.d(LOG_TAG, "About to Install package from " + urlToApk);
Intent intent = new Intent(Intent.ACTION_VIEW);
packageuri = NougatUtil.getPackageUri(form, apkfile);
intent.setDataAndType(packageuri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
form.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.e(LOG_TAG, "Unable to install package", e);
form.dispatchErrorOccurredEvent(form, "CompanionExtras",
ErrorMessages.ERROR_UNABLE_TO_INSTALL_PACKAGE, packageuri);
} catch (Exception e) {
Log.e(LOG_TAG, "ERROR_UNABLE_TO_GET", e);
form.dispatchErrorOccurredEvent(form, "CompanionExtras",
ErrorMessages.ERROR_WEB_UNABLE_TO_GET, urlToApk);
}
}
});
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment