1 /* 2 * Copyright (C) 2011 The Android Open Source Project 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 vogar.android; 18 19 import java.io.File; 20 import java.io.FileOutputStream; 21 import java.io.IOException; 22 import vogar.Action; 23 import vogar.Classpath; 24 import vogar.Result; 25 import vogar.Run; 26 import vogar.TestProperties; 27 import vogar.commands.Command; 28 import vogar.tasks.Task; 29 30 public final class InstallApkTask extends Task { 31 public static final String ACTIVITY_CLASS = "vogar.target.TestActivity"; 32 33 private final Action action; 34 private final File jar; 35 private final Run run; 36 InstallApkTask(Run run, Action action, File jar)37 public InstallApkTask(Run run, Action action, File jar) { 38 super("aapt and push " + action.getName()); 39 this.action = action; 40 this.jar = jar; 41 this.run = run; 42 } 43 execute()44 @Override protected Result execute() throws Exception { 45 // We can't put multiple dex files in one apk. 46 // We can't just give dex multiple jars with conflicting class names 47 48 // With that in mind, the APK packaging strategy is as follows: 49 // 1. dx to create a dex 50 // 2. aapt the dex to create apk 51 // 3. sign the apk 52 // 4. install the apk 53 File dex = createDex(action, jar); 54 File apk = createApk(action, dex); 55 signApk(apk); 56 installApk(action, apk); 57 return Result.SUCCESS; 58 } 59 60 /** 61 * Returns a single dexfile containing {@code action}'s classes and all 62 * dependencies. 63 */ createDex(Action action, File actionJar)64 private File createDex(Action action, File actionJar) { 65 File dex = run.localFile(action, "classes.dex"); 66 Classpath classesToDex = Classpath.of(actionJar); 67 classesToDex.addAll(run.classpath); 68 if (run.useJack) { 69 // TODO Implement Jack support for mode=activity. 70 throw new UnsupportedOperationException( 71 "Jack support for --mode=activity not yet implemented"); 72 } 73 74 File localTempDir = run.localDir(action.getName()); 75 76 // Do not specify additional compile-time-only dependencies, 77 // because everything gets bundled into classes.dex. 78 Classpath dependentCp = new Classpath(); 79 80 run.androidSdk.dex(run.multidex, dex, localTempDir, classesToDex, dependentCp); 81 return dex; 82 } 83 createApk(Action action, File dex)84 private File createApk (Action action, File dex) { 85 String androidManifest = 86 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + 87 "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + 88 " package=\"" + packageName(action) + "\">\n" + 89 " <uses-permission android:name=\"android.permission.INTERNET\" />\n" + 90 " <application" + 91 ((run.debugging) ? " android:debuggable=\"true\"" : "") + ">\n" + 92 " <activity android:name=\"" + ACTIVITY_CLASS + "\">\n" + 93 " <intent-filter>\n" + 94 " <action android:name=\"android.intent.action.MAIN\" />\n" + 95 " <category android:name=\"android.intent.category.LAUNCHER\" />\n" + 96 " </intent-filter>\n" + 97 " </activity>\n" + 98 " </application>\n" + 99 "</manifest>\n"; 100 File androidManifestFile = run.localFile(action, "classes", "AndroidManifest.xml"); 101 try { 102 FileOutputStream androidManifestOut = 103 new FileOutputStream(androidManifestFile); 104 androidManifestOut.write(androidManifest.getBytes("UTF-8")); 105 androidManifestOut.close(); 106 } catch (IOException e) { 107 throw new RuntimeException("Problem writing " + androidManifestFile, e); 108 } 109 110 File apk = run.localFile(action, action + ".apk"); 111 run.androidSdk.packageApk(apk, androidManifestFile); 112 run.androidSdk.addToApk(apk, dex); 113 run.androidSdk.addToApk(apk, run.localFile(action, "classes", TestProperties.FILE)); 114 return apk; 115 } 116 117 /** 118 * According to android.content.pm.PackageParser, package name 119 * "must have at least one '.' separator" Since the qualified name 120 * may not contain a dot, we prefix containing one to ensure we 121 * are compliant. 122 */ packageName(Action action)123 public static String packageName(Action action) { 124 return "vogar.test." + action.getName(); 125 } 126 signApk(File apkUnsigned)127 private void signApk(File apkUnsigned) { 128 /* 129 * key generated with this command, using "password" for the key and keystore passwords: 130 * keytool -genkey -v -keystore src/vogar/vogar.keystore \ 131 * -keyalg RSA -validity 10000 -alias vogar 132 */ 133 new Command(run.log, "jarsigner", 134 "--storepass", "password", 135 "-keystore", run.keystore.getPath(), 136 apkUnsigned.getPath(), 137 "vogar") 138 .execute(); 139 } 140 installApk(Action action, File apkSigned)141 private void installApk(Action action, File apkSigned) { 142 // install the local apk ona the device 143 run.androidSdk.uninstall(packageName(action)); 144 run.androidSdk.install(apkSigned); 145 } 146 } 147