chiark / gitweb /
The work! The progress!
[tripe-android] / app.scala
1 /* -*-scala-*-
2  *
3  * Setting up the Android environment
4  *
5  * (c) 2018 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the Trivial IP Encryption (TrIPE) Android app.
11  *
12  * TrIPE is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your
15  * option) any later version.
16  *
17  * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
20  * for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
24  */
25
26 package uk.org.distorted.tripe; package object app {
27
28 /*----- Imports -----------------------------------------------------------*/
29
30 import java.io.{File, IOException};
31
32 import scala.collection.mutable.HashMap;
33
34 import android.content.Context; import Context.MODE_WORLD_READABLE;
35 import android.os.Build; import Build.{CPU_ABI, CPU_ABI2};
36 import android.util.Log;
37
38 import sys.FileImplicits._;
39
40 /*----- Regular expressions for parsing the `.installed file --------------*/
41
42 private final val RX_COMMENT = """(?x) ^ \s* (?: \# .* )? $""".r;
43 private final val RX_KEYVAL = """(?x) ^ \s*
44       ([-\w]+)
45       (?:\s+(?!=)|\s*=\s*)
46       (|\S|\S.*\S)
47       \s* $""".r;
48
49 /*----- Main code ---------------------------------------------------------*/
50
51 private final val TAG = "TrIPE";
52
53 var root: File = null;
54
55 private def install(ctx: Context, inst: HashMap[String, String]) {
56
57   /* First, figure out which ABIs are wanted on this device.  Unfortunately,
58    * the good way of doing this isn't available in our minimum API level, so
59    * we must use reflection.
60    */
61   val abis = try {
62     classOf[Build].getField("SUPPORTED_ABIS").get(null).
63       asInstanceOf[Array[String]]
64   } catch { case _: NoSuchFieldException =>
65     Array(CPU_ABI, CPU_ABI2) flatMap {
66       case null | "" => None
67       case s => Some(s)
68     }
69   }
70   Log.d(TAG, s"abis = ${abis.mkString(", ")}");
71
72   /* Clear out whatever might be there already. */
73   val bindir = root/"bin";
74   bindir.rmTree();
75   bindir.mkdir_!();
76
77   /* Now extract each of our binaries using the best available ABI. */
78   val assets = ctx.getAssets;
79   for (abi <- abis) {
80     val binsrc = s"bin/$abi";
81     for (base <- assets.list(binsrc)) {
82       val outfile = bindir/base;
83       if (!outfile.exists_!) {
84         Log.d(TAG, s"install: extract `$base' using abi `$abi'");
85         outfile.withOutput { out =>
86           closing(assets.open(s"$binsrc/$base")) { in =>
87             for ((buf, n) <- blocks(in)) out.write(buf, 0, n);
88           }
89         }
90       }
91       outfile.chmod_!(0x1ed);
92     }
93   }
94
95   /* Write out a new install file. */
96   val infofile = root/".installed";
97   val newinfofile = root/".installed.new";
98   newinfofile.withWriter { out =>
99     out.write(s"""### -*-conf-*-
100
101 uuid = ${ctx.getString(R.string.auto_build_uuid)}
102 """);
103   }
104   newinfofile.rename_!(infofile);
105 }
106
107 def setup(ctx: Context) {
108
109   /* Make our root directory and remember where it is. */
110   root = ctx.getFilesDir;
111   if (!root.isdir_!) {
112     throw new IOException("system failed to create `files' " +
113                           "(but didn't tell us)");
114   }
115
116   /* Find out which build, if any, corresponds to what's there already. */
117   val inst = HashMap[String, String]();
118   try { root/".installed" withReader { in =>
119     var lno = 1;
120     for (line <- lines(in)) {
121       line match {
122         case RX_COMMENT() => ok;
123         case RX_KEYVAL(k, v) => inst(k) = v;
124         case _ => Log.w(TAG, s".installed:$lno: ignored unparseable line");
125       }
126       lno += 1;
127     }
128   } } catch {
129     case e: IOException =>
130       Log.w(TAG, s".installed: I/O error: ${e.getMessage}");
131   }
132
133   /* If this doesn't match, then we have some work to do. */
134   if (inst.getOrElse("uuid", "<nothing>") !=
135       ctx.getString(R.string.auto_build_uuid))
136     install(ctx, inst);
137 }
138
139 /*----- That's all, folks -------------------------------------------------*/
140
141 }