AndroidSdkTool.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. /*
  2. * To change this template, choose Tools | Templates
  3. * and open the template in the editor.
  4. */
  5. package com.jme3.gde.android;
  6. import java.io.BufferedOutputStream;
  7. import java.io.File;
  8. import java.io.IOException;
  9. import java.io.InputStream;
  10. import java.io.OutputStream;
  11. import java.io.OutputStreamWriter;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import java.util.logging.Level;
  15. import java.util.logging.Logger;
  16. import org.netbeans.api.project.Project;
  17. import org.openide.DialogDisplayer;
  18. import org.openide.NotifyDescriptor;
  19. import org.openide.NotifyDescriptor.Message;
  20. import org.openide.filesystems.FileChooserBuilder;
  21. import org.openide.filesystems.FileLock;
  22. import org.openide.filesystems.FileObject;
  23. import org.openide.filesystems.FileUtil;
  24. import org.openide.util.Exceptions;
  25. import org.openide.util.NbPreferences;
  26. import org.openide.util.Utilities;
  27. import org.openide.xml.XMLUtil;
  28. import org.w3c.dom.Document;
  29. import org.w3c.dom.Element;
  30. import org.xml.sax.InputSource;
  31. import org.xml.sax.SAXException;
  32. /**
  33. *
  34. * @author normenhansen
  35. */
  36. public class AndroidSdkTool {
  37. /**
  38. * Starts the Android target configuration utility.
  39. */
  40. public static void startAndroidTool() {
  41. startAndroidTool(false);
  42. }
  43. public static void startAndroidTool(boolean modal) {
  44. final String path = getAndroidToolPath();
  45. if (path == null) {
  46. return;
  47. }
  48. Thread thread = new Thread(new Runnable() {
  49. @Override
  50. public void run() {
  51. String[] command = new String[]{path};
  52. ProcessBuilder builder = new ProcessBuilder(command);
  53. try {
  54. Process proc = builder.start();
  55. OutputReader outReader = new OutputReader(proc.getInputStream());
  56. OutputReader errReader = new OutputReader(proc.getErrorStream());
  57. outReader.start();
  58. errReader.start();
  59. proc.waitFor();
  60. } catch (InterruptedException ex) {
  61. Exceptions.printStackTrace(ex);
  62. } catch (IOException ex) {
  63. Exceptions.printStackTrace(ex);
  64. }
  65. }
  66. });
  67. if (modal) {
  68. thread.run();
  69. } else {
  70. thread.start();
  71. }
  72. }
  73. /**
  74. * Returns a FileObject for the android SDK folder, null if none is specified
  75. * @return
  76. */
  77. public static FileObject getSdkFolder() {
  78. String path = getSdkPath();
  79. if (path == null) {
  80. return null;
  81. }
  82. FileObject fileObj = FileUtil.toFileObject(new File(path));
  83. if (fileObj == null) {
  84. return null;
  85. }
  86. return fileObj;
  87. }
  88. /**
  89. * Returns a String with the path to the SDK or null if none is specified.
  90. * @return
  91. */
  92. public static String getSdkPath() {
  93. String path = NbPreferences.forModule(AndroidSdkTool.class).get("sdk_path", null);
  94. if (path == null) {
  95. FileChooserBuilder builder = new FileChooserBuilder(AndroidSdkTool.class);
  96. builder.setTitle("Please select Android SDK Folder");
  97. builder.setDirectoriesOnly(true);
  98. File file = builder.showOpenDialog();
  99. if (file != null) {
  100. FileObject folder = FileUtil.toFileObject(file);
  101. if (folder.getFileObject("tools") == null) {
  102. Message msg = new NotifyDescriptor.Message(
  103. "Not a valid SDK folder!",
  104. NotifyDescriptor.ERROR_MESSAGE);
  105. DialogDisplayer.getDefault().notifyLater(msg);
  106. } else {
  107. String name = file.getPath();
  108. NbPreferences.forModule(AndroidSdkTool.class).put("sdk_path", name);
  109. return name;
  110. }
  111. }
  112. } else {
  113. return path;
  114. }
  115. return null;
  116. }
  117. /**
  118. * Returns a string with the path to the android tool, specific for platform (.exe for windows)
  119. * @return
  120. */
  121. public static String getAndroidToolPath() {
  122. FileObject executable = null;
  123. FileObject folder = getSdkFolder();
  124. if (folder == null) {
  125. return null;
  126. }
  127. if (Utilities.isWindows()) {
  128. executable = folder.getFileObject("tools/android.bat");
  129. } else {
  130. executable = folder.getFileObject("tools/android");
  131. }
  132. if (executable != null) {
  133. return FileUtil.toFile(executable).getPath();
  134. } else {
  135. return null;
  136. }
  137. }
  138. /**
  139. * Gets a list of android targets registered in the SDK
  140. * @return
  141. */
  142. public static List<AndroidTarget> getTargetList() {
  143. ArrayList<AndroidTarget> list = new ArrayList<AndroidTarget>();
  144. final String path = getAndroidToolPath();
  145. if (path == null) {
  146. return list;
  147. }
  148. String[] command = new String[]{path, "list", "targets"};
  149. ProcessBuilder builder = new ProcessBuilder(command);
  150. try {
  151. Process proc = builder.start();
  152. ListReader outReader = new ListReader(proc.getInputStream(), list);
  153. OutputReader errReader = new OutputReader(proc.getErrorStream());
  154. outReader.start();
  155. errReader.start();
  156. proc.waitFor();
  157. } catch (InterruptedException ex) {
  158. Exceptions.printStackTrace(ex);
  159. } catch (IOException ex) {
  160. Exceptions.printStackTrace(ex);
  161. }
  162. return list;
  163. }
  164. //TODO: check mainJmeClass
  165. public static void checkProject(Project project, String target, String name, String activity, String packag, String mainJmeClass) {
  166. final String path = getAndroidToolPath();
  167. if (path == null) {
  168. return;
  169. }
  170. FileObject folder = project.getProjectDirectory().getFileObject("mobile");
  171. if (folder == null) {
  172. try {
  173. folder = project.getProjectDirectory().createFolder("mobile");
  174. createProject(project, target, name, activity, packag, mainJmeClass);
  175. } catch (IOException ex) {
  176. Exceptions.printStackTrace(ex);
  177. return;
  178. }
  179. } else {
  180. updateProject(project, target, name);
  181. }
  182. }
  183. public static void createProject(Project project, String target, String name, String activity, String packag, String mainJmeClass) {
  184. final String path = getAndroidToolPath();
  185. if (path == null) {
  186. return;
  187. }
  188. FileObject folder = project.getProjectDirectory().getFileObject("mobile");
  189. if (folder == null) {
  190. try {
  191. folder = project.getProjectDirectory().createFolder("mobile");
  192. } catch (IOException ex) {
  193. Exceptions.printStackTrace(ex);
  194. return;
  195. }
  196. }
  197. String[] command = new String[]{path, "create", "project",
  198. "--target", target,
  199. "--name", name,
  200. "--path", FileUtil.toFile(folder).getPath(),
  201. "--activity", activity,
  202. "--package", packag};
  203. ProcessBuilder builder = new ProcessBuilder(command);
  204. FileLock lock = null;
  205. try {
  206. Process proc = builder.start();
  207. OutputReader outReader = new OutputReader(proc.getInputStream());
  208. OutputReader errReader = new OutputReader(proc.getErrorStream());
  209. outReader.start();
  210. errReader.start();
  211. proc.waitFor();
  212. folder.refresh();
  213. String mainActName = "mobile/src/" + packag.replaceAll("\\.", "/") + "/MainActivity.java";
  214. FileObject mainAct = project.getProjectDirectory().getFileObject(mainActName);
  215. if (mainAct != null) {
  216. lock = mainAct.lock();
  217. OutputStreamWriter out = new OutputStreamWriter(new BufferedOutputStream(mainAct.getOutputStream(lock)));
  218. out.write(mainActivityString(mainJmeClass, packag));
  219. out.close();
  220. lock.releaseLock();
  221. } else {
  222. Logger.getLogger(AndroidSdkTool.class.getName()).log(Level.WARNING, "Cannot find {0}", mainActName);
  223. }
  224. } catch (InterruptedException ex) {
  225. Exceptions.printStackTrace(ex);
  226. } catch (IOException ex) {
  227. if (lock != null) {
  228. lock.releaseLock();
  229. }
  230. Exceptions.printStackTrace(ex);
  231. }
  232. updateAndroidManifest(project);
  233. updateAndroidApplicationName(project, name);
  234. }
  235. public static void updateProject(Project project, String target, String name) {
  236. final String path = getAndroidToolPath();
  237. if (path == null) {
  238. return;
  239. }
  240. FileObject folder = project.getProjectDirectory().getFileObject("mobile");
  241. if (folder == null) {
  242. return;
  243. }
  244. String[] command = new String[]{path, "update", "project",
  245. "--target", target,
  246. "--name", name,
  247. "--path", FileUtil.toFile(folder).getPath()};
  248. ProcessBuilder builder = new ProcessBuilder(command);
  249. try {
  250. Process proc = builder.start();
  251. OutputReader outReader = new OutputReader(proc.getInputStream());
  252. OutputReader errReader = new OutputReader(proc.getErrorStream());
  253. outReader.start();
  254. errReader.start();
  255. proc.waitFor();
  256. folder.refresh();
  257. } catch (InterruptedException ex) {
  258. Exceptions.printStackTrace(ex);
  259. } catch (IOException ex) {
  260. Exceptions.printStackTrace(ex);
  261. }
  262. updateAndroidApplicationName(project, name);
  263. }
  264. private static void updateAndroidManifest(Project project) {
  265. FileObject manifest = project.getProjectDirectory().getFileObject("mobile/AndroidManifest.xml");
  266. if (manifest == null) {
  267. Logger.getLogger(AndroidSdkTool.class.getName()).log(Level.WARNING, "Could not find AndroidManifest.xml");
  268. return;
  269. }
  270. InputStream in = null;
  271. FileLock lock = null;
  272. OutputStream out = null;
  273. try {
  274. in = manifest.getInputStream();
  275. Document configuration = XMLUtil.parse(new InputSource(in), false, false, null, null);
  276. in.close();
  277. in = null;
  278. boolean changed = false;
  279. Element sdkApplication = XmlHelper.findChildElement(configuration.getDocumentElement(), "application");
  280. if (sdkApplication != null) {
  281. Element sdkActivity = XmlHelper.findChildElement(sdkApplication, "activity");
  282. if (sdkActivity != null) {
  283. if (!sdkActivity.hasAttribute("android:launchMode")) {
  284. sdkActivity.setAttribute("android:launchMode", "singleTask");
  285. changed = true;
  286. }
  287. }
  288. // add the following after AndroidHarness.screenOrientation is depreciated
  289. // for jME 3.1
  290. // if (sdkActivity != null) {
  291. // if (sdkActivity.hasAttribute("android:screenOrientation")) {
  292. // String attrScreenOrientation = sdkActivity.getAttribute("android:screenOrientation");
  293. // } else {
  294. // Logger.getLogger(AndroidSdkTool.class.getName()).log(Level.INFO, "creating attrScreenOrientation");
  295. // sdkActivity.setAttribute("android:screenOrientation", "landscape");
  296. // changed = true;
  297. // }
  298. // }
  299. }
  300. Element sdkElement = XmlHelper.findChildElement(configuration.getDocumentElement(), "uses-sdk");
  301. if (sdkElement == null) {
  302. sdkElement = configuration.createElement("uses-sdk");
  303. configuration.getDocumentElement().appendChild(sdkElement);
  304. changed = true;
  305. }
  306. if (!"8".equals(sdkElement.getAttribute("android:minSdkVersion"))) {
  307. sdkElement.setAttribute("android:minSdkVersion", "8");
  308. changed = true;
  309. }
  310. Element screensElement = XmlHelper.findChildElement(configuration.getDocumentElement(), "supports-screens");
  311. if (screensElement == null) {
  312. screensElement = configuration.createElement("supports-screens");
  313. screensElement.setAttribute("android:anyDensity", "true");
  314. // screensElement.setAttribute("android:xlargeScreens", "true");
  315. screensElement.setAttribute("android:largeScreens", "true");
  316. screensElement.setAttribute("android:smallScreens", "true");
  317. screensElement.setAttribute("android:normalScreens", "true");
  318. configuration.getDocumentElement().appendChild(screensElement);
  319. changed = true;
  320. }
  321. if (changed) {
  322. lock = manifest.lock();
  323. out = manifest.getOutputStream(lock);
  324. XMLUtil.write(configuration, out, "UTF-8");
  325. out.close();
  326. out = null;
  327. lock.releaseLock();
  328. lock = null;
  329. }
  330. } catch (SAXException ex) {
  331. Exceptions.printStackTrace(ex);
  332. } catch (IOException ex) {
  333. Exceptions.printStackTrace(ex);
  334. } finally {
  335. if (lock != null) {
  336. lock.releaseLock();
  337. }
  338. try {
  339. if (in != null) {
  340. in.close();
  341. }
  342. if (out != null) {
  343. out.close();
  344. }
  345. } catch (IOException ex1) {
  346. Exceptions.printStackTrace(ex1);
  347. }
  348. }
  349. }
  350. private static void updateAndroidApplicationName(Project project, String name) {
  351. FileObject manifest = project.getProjectDirectory().getFileObject("mobile/res/values/strings.xml");
  352. if (manifest == null) {
  353. return;
  354. }
  355. InputStream in = null;
  356. FileLock lock = null;
  357. OutputStream out = null;
  358. try {
  359. in = manifest.getInputStream();
  360. Document configuration = XMLUtil.parse(new InputSource(in), false, false, null, null);
  361. in.close();
  362. in = null;
  363. Element sdkElement = XmlHelper.findChildElementWithAttribute(configuration.getDocumentElement(), "string", "name", "app_name");
  364. if (sdkElement == null) {
  365. sdkElement = configuration.createElement("string");
  366. sdkElement.setAttribute("name", "app_name");
  367. configuration.getDocumentElement().appendChild(sdkElement);
  368. }
  369. if (!sdkElement.getTextContent().trim().equals(name)) {
  370. sdkElement.setTextContent(name);
  371. lock = manifest.lock();
  372. out = manifest.getOutputStream(lock);
  373. XMLUtil.write(configuration, out, "UTF-8");
  374. out.close();
  375. out = null;
  376. lock.releaseLock();
  377. lock = null;
  378. }
  379. } catch (SAXException ex) {
  380. Exceptions.printStackTrace(ex);
  381. } catch (IOException ex) {
  382. Exceptions.printStackTrace(ex);
  383. } finally {
  384. if (lock != null) {
  385. lock.releaseLock();
  386. }
  387. try {
  388. if (in != null) {
  389. in.close();
  390. }
  391. if (out != null) {
  392. out.close();
  393. }
  394. } catch (IOException ex1) {
  395. Exceptions.printStackTrace(ex1);
  396. }
  397. }
  398. }
  399. private static String mainActivityString(String mainClass, String packag) {
  400. String str =
  401. "package " + packag + ";\n"
  402. + " \n"
  403. + "import android.content.pm.ActivityInfo;\n"
  404. + "import com.jme3.app.AndroidHarness;\n"
  405. + "import com.jme3.system.android.AndroidConfigChooser.ConfigType;\n"
  406. + "import java.util.logging.Level;\n"
  407. + "import java.util.logging.LogManager;\n"
  408. + " \n"
  409. + "public class MainActivity extends AndroidHarness{\n"
  410. + " \n"
  411. + " /*\n"
  412. + " * Note that you can ignore the errors displayed in this file,\n"
  413. + " * the android project will build regardless.\n"
  414. + " * Install the 'Android' plugin under Tools->Plugins->Available Plugins\n"
  415. + " * to get error checks and code completion for the Android project files.\n"
  416. + " */\n"
  417. + " \n"
  418. + " public MainActivity(){\n"
  419. + " // Set the application class to run\n"
  420. + " appClass = \"" + mainClass + "\";\n"
  421. + " // Try ConfigType.FASTEST; or ConfigType.LEGACY if you have problems\n"
  422. + " eglConfigType = ConfigType.BEST;\n"
  423. + " // Exit Dialog title & message\n"
  424. + " exitDialogTitle = \"Exit?\";\n"
  425. + " exitDialogMessage = \"Press Yes\";\n"
  426. + " // Enable verbose logging\n"
  427. + " eglConfigVerboseLogging = false;\n"
  428. + " // Choose screen orientation\n"
  429. + " screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;\n"
  430. + " // Enable MouseEvents being generated from TouchEvents (default = true)\n"
  431. + " mouseEventsEnabled = true;\n"
  432. + " // Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info)\n"
  433. + " LogManager.getLogManager().getLogger(\"\").setLevel(Level.INFO);\n"
  434. + " }\n"
  435. + " \n"
  436. + "}\n";
  437. return str;
  438. }
  439. public static class AndroidTarget {
  440. private int id;
  441. private String name;
  442. private String title;
  443. private String platform;
  444. private int apiLevel;
  445. private int revision;
  446. private String skins;
  447. public AndroidTarget() {
  448. }
  449. public AndroidTarget(String name) {
  450. this.name = name;
  451. }
  452. public int getId() {
  453. return id;
  454. }
  455. public void setId(int id) {
  456. this.id = id;
  457. }
  458. public String getName() {
  459. return name;
  460. }
  461. public void setName(String name) {
  462. this.name = name;
  463. }
  464. public String getTitle() {
  465. return title;
  466. }
  467. public void setTitle(String title) {
  468. this.title = title;
  469. }
  470. public String getPlatform() {
  471. return platform;
  472. }
  473. public void setPlatform(String platform) {
  474. this.platform = platform;
  475. }
  476. public int getApiLevel() {
  477. return apiLevel;
  478. }
  479. public void setApiLevel(int apiLevel) {
  480. this.apiLevel = apiLevel;
  481. }
  482. public int getRevision() {
  483. return revision;
  484. }
  485. public void setRevision(int revision) {
  486. this.revision = revision;
  487. }
  488. public String getSkins() {
  489. return skins;
  490. }
  491. public void setSkins(String skins) {
  492. this.skins = skins;
  493. }
  494. @Override
  495. public String toString() {
  496. return getTitle();
  497. }
  498. @Override
  499. public boolean equals(Object obj) {
  500. if (obj instanceof String && getName() != null) {
  501. return getName().equals(obj);
  502. } else {
  503. return super.equals(obj);
  504. }
  505. }
  506. }
  507. }