• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.settings.development;
18 
19 import android.app.ListActivity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.SystemProperties;
25 import android.text.TextUtils;
26 import android.util.Slog;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.ArrayAdapter;
31 import android.widget.ListView;
32 
33 import com.android.settings.R;
34 
35 import org.json.JSONArray;
36 import org.json.JSONException;
37 import org.json.JSONObject;
38 
39 import java.io.BufferedInputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.net.MalformedURLException;
43 import java.net.URL;
44 import java.text.ParseException;
45 import java.text.SimpleDateFormat;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Date;
49 import java.util.List;
50 
51 import javax.net.ssl.HttpsURLConnection;
52 
53 /**
54  * DSU Loader is a front-end that offers developers the ability to boot into GSI with one-click. It
55  * also offers the flexibility to overwrite the default setting and load OEMs owned images.
56  */
57 public class DSULoader extends ListActivity {
58     private static final int Q_VNDK_BASE = 28;
59     private static final int Q_OS_BASE = 10;
60 
61     private static final boolean DEBUG = false;
62     private static final String TAG = "DSULOADER";
63     private static final String PROPERTY_KEY_CPU = "ro.product.cpu.abi";
64     private static final String PROPERTY_KEY_OS = "ro.system.build.version.release";
65     private static final String PROPERTY_KEY_VNDK = "ro.vndk.version";
66     private static final String PROPERTY_KEY_LIST =
67             "persist.sys.fflag.override.settings_dynamic_system.list";
68     private static final String PROPERTY_KEY_SPL = "ro.build.version.security_patch";
69     private static final String DSU_LIST =
70             "https://dl.google.com/developers/android/gsi/gsi-src.json";
71 
72     private static final int TIMEOUT_MS = 10 * 1000;
73     private List<Object> mDSUList = new ArrayList<Object>();
74     private ArrayAdapter<Object> mAdapter;
75 
readAll(InputStream in)76     private static String readAll(InputStream in) throws IOException {
77         int n;
78         StringBuilder list = new StringBuilder();
79         byte[] bytes = new byte[4096];
80         while ((n = in.read(bytes, 0, 4096)) != -1) {
81             list.append(new String(Arrays.copyOf(bytes, n)));
82         }
83         return list.toString();
84     }
85 
readAll(URL url)86     private static String readAll(URL url) throws IOException {
87         InputStream in = null;
88         HttpsURLConnection connection = null;
89         Slog.i(TAG, "fetch " + url.toString());
90         try {
91             connection = (HttpsURLConnection) url.openConnection();
92             connection.setReadTimeout(TIMEOUT_MS);
93             connection.setConnectTimeout(TIMEOUT_MS);
94             connection.setRequestMethod("GET");
95             connection.setDoInput(true);
96             connection.connect();
97             int responseCode = connection.getResponseCode();
98             if (connection.getResponseCode() != HttpsURLConnection.HTTP_OK) {
99                 throw new IOException("HTTP error code: " + responseCode);
100             }
101             in = new BufferedInputStream(connection.getInputStream());
102             return readAll(in);
103         } catch (Exception e) {
104             throw e;
105         } finally {
106             try {
107                 if (in != null) {
108                     in.close();
109                     in = null;
110                 }
111             } catch (IOException e) {
112                 // ignore
113             }
114             if (connection != null) {
115                 connection.disconnect();
116                 connection = null;
117             }
118         }
119     }
120     // Fetcher fetches mDSUList in backgroud
121     private class Fetcher implements Runnable {
122         private URL mDsuList;
123 
Fetcher(URL dsuList)124         Fetcher(URL dsuList) {
125             mDsuList = dsuList;
126         }
127 
fetch(URL url)128         private void fetch(URL url)
129                 throws IOException, JSONException, MalformedURLException, ParseException {
130             String content = readAll(url);
131             JSONObject jsn = new JSONObject(content);
132             // The include primitive is like below
133             // "include": [
134             //   "https:/...json",
135             //    ...
136             // ]
137             if (jsn.has("include")) {
138                 JSONArray include = jsn.getJSONArray("include");
139                 int len = include.length();
140                 for (int i = 0; i < len; i++) {
141                     if (include.isNull(i)) {
142                         continue;
143                     }
144                     fetch(new URL(include.getString(i)));
145                 }
146             }
147             //  "images":[
148             //    {
149             //      "name":"...",
150             //      "os_version":"10",
151             //      "cpu_abi":"...",
152             //      "details":"...",
153             //      "vndk":[],
154             //      "spl":"...",
155             //      "pubkey":"",
156             //      "uri":"https://...zip"
157             //    },
158             //     ...
159             //  ]
160             if (jsn.has("images")) {
161                 JSONArray images = jsn.getJSONArray("images");
162                 int len = images.length();
163                 for (int i = 0; i < len; i++) {
164                     DSUPackage dsu = new DSUPackage(images.getJSONObject(i));
165                     if (dsu.isSupported()) {
166                         mDSUList.add(dsu);
167                     }
168                 }
169             }
170         }
171 
run()172         public void run() {
173             try {
174                 fetch(mDsuList);
175             } catch (IOException e) {
176                 Slog.e(TAG, e.toString());
177                 mDSUList.add(0, "Network Error");
178             } catch (Exception e) {
179                 Slog.e(TAG, e.toString());
180                 mDSUList.add(0, "Metadata Error");
181             }
182             if (mDSUList.size() == 0) {
183                 mDSUList.add(0, "No DSU available for this device");
184             }
185             runOnUiThread(
186                     new Runnable() {
187                         public void run() {
188                             mAdapter.clear();
189                             mAdapter.addAll(mDSUList);
190                         }
191                     });
192         }
193     }
194 
195     private class DSUPackage {
196         private static final String NAME = "name";
197         private static final String DETAILS = "details";
198         private static final String CPU_ABI = "cpu_abi";
199         private static final String URI = "uri";
200         private static final String OS_VERSION = "os_version";
201         private static final String VNDK = "vndk";
202         private static final String PUBKEY = "pubkey";
203         private static final String SPL = "spl";
204         private static final String SPL_FORMAT = "yyyy-MM-dd";
205         private static final String TOS = "tos";
206 
207         String mName = null;
208         String mDetails = null;
209         String mCpuAbi = null;
210         int mOsVersion = -1;
211         int[] mVndk = null;
212         String mPubKey = "";
213         Date mSPL = null;
214         URL mTosUrl = null;
215         URL mUri;
216 
DSUPackage(JSONObject jsn)217         DSUPackage(JSONObject jsn) throws JSONException, MalformedURLException, ParseException {
218             Slog.i(TAG, "DSUPackage: " + jsn.toString());
219             mName = jsn.getString(NAME);
220             mDetails = jsn.getString(DETAILS);
221             mCpuAbi = jsn.getString(CPU_ABI);
222             mUri = new URL(jsn.getString(URI));
223             if (jsn.has(OS_VERSION)) {
224                 mOsVersion = dessertNumber(jsn.getString(OS_VERSION), Q_OS_BASE);
225             }
226             if (jsn.has(VNDK)) {
227                 JSONArray vndks = jsn.getJSONArray(VNDK);
228                 mVndk = new int[vndks.length()];
229                 for (int i = 0; i < vndks.length(); i++) {
230                     mVndk[i] = vndks.getInt(i);
231                 }
232             }
233             if (jsn.has(PUBKEY)) {
234                 mPubKey = jsn.getString(PUBKEY);
235             }
236             if (jsn.has(TOS)) {
237                 mTosUrl = new URL(jsn.getString(TOS));
238             }
239             if (jsn.has(SPL)) {
240                 mSPL = new SimpleDateFormat(SPL_FORMAT).parse(jsn.getString(SPL));
241             }
242         }
243 
dessertNumber(String s, int base)244         int dessertNumber(String s, int base) {
245             if (s == null || s.isEmpty()) {
246                 return -1;
247             }
248             if (Character.isDigit(s.charAt(0))) {
249                 return Integer.parseInt(s);
250             } else {
251                 s = s.toUpperCase();
252                 return ((int) s.charAt(0) - (int) 'Q') + base;
253             }
254         }
255 
getDeviceVndk()256         int getDeviceVndk() {
257             if (DEBUG) {
258                 return Q_VNDK_BASE;
259             }
260             return dessertNumber(SystemProperties.get(PROPERTY_KEY_VNDK), Q_VNDK_BASE);
261         }
262 
getDeviceOs()263         int getDeviceOs() {
264             if (DEBUG) {
265                 return Q_OS_BASE;
266             }
267             return dessertNumber(SystemProperties.get(PROPERTY_KEY_OS), Q_OS_BASE);
268         }
269 
getDeviceCpu()270         String getDeviceCpu() {
271             String cpu = SystemProperties.get(PROPERTY_KEY_CPU);
272             cpu = cpu.toLowerCase();
273             if (cpu.startsWith("aarch64")) {
274                 cpu = "arm64-v8a";
275             }
276             return cpu;
277         }
278 
getDeviceSPL()279         Date getDeviceSPL() {
280             String spl = SystemProperties.get(PROPERTY_KEY_SPL);
281             if (TextUtils.isEmpty(spl)) {
282                 return null;
283             }
284             try {
285                 return new SimpleDateFormat(SPL_FORMAT).parse(spl);
286             } catch (ParseException e) {
287                 return null;
288             }
289         }
290 
isSupported()291         boolean isSupported() {
292             boolean supported = true;
293             String cpu = getDeviceCpu();
294             if (!mCpuAbi.equals(cpu)) {
295                 Slog.i(TAG, mCpuAbi + " != " + cpu);
296                 supported = false;
297             }
298             if (mOsVersion > 0) {
299                 int os = getDeviceOs();
300                 if (os < 0) {
301                     Slog.i(TAG, "Failed to getDeviceOs");
302                     supported = false;
303                 } else if (mOsVersion < os) {
304                     Slog.i(TAG, mOsVersion + " < " + os);
305                     supported = false;
306                 }
307             }
308             if (mVndk != null) {
309                 int vndk = getDeviceVndk();
310                 if (vndk < 0) {
311                     Slog.i(TAG, "Failed to getDeviceVndk");
312                     supported = false;
313                 } else {
314                     boolean found_vndk = false;
315                     for (int i = 0; i < mVndk.length; i++) {
316                         if (mVndk[i] == vndk) {
317                             found_vndk = true;
318                             break;
319                         }
320                     }
321                     if (!found_vndk) {
322                         Slog.i(TAG, "vndk:" + vndk + " not found");
323                         supported = false;
324                     }
325                 }
326             }
327             if (mSPL != null) {
328                 Date spl = getDeviceSPL();
329                 if (spl == null) {
330                     Slog.i(TAG, "Failed to getDeviceSPL");
331                     supported = false;
332                 } else if (spl.getTime() > mSPL.getTime()) {
333                     Slog.i(TAG, "Device SPL:" + spl.toString() + " > " + mSPL.toString());
334                     supported = false;
335                 }
336             }
337             Slog.i(TAG, mName + " isSupported " + supported);
338             return supported;
339         }
340     }
341 
342     @Override
onCreate(Bundle icicle)343     protected void onCreate(Bundle icicle) {
344         super.onCreate(icicle);
345         String dsuList = SystemProperties.get(PROPERTY_KEY_LIST);
346         Slog.e(TAG, "Try to get DSU list from: " + PROPERTY_KEY_LIST);
347         if (dsuList == null || dsuList.isEmpty()) {
348             dsuList = DSU_LIST;
349         }
350         Slog.e(TAG, "DSU list: " + dsuList);
351         URL url = null;
352         try {
353             url = new URL(dsuList);
354         } catch (MalformedURLException e) {
355             Slog.e(TAG, e.toString());
356             return;
357         }
358         mAdapter = new DSUPackageListAdapter(this);
359         setListAdapter(mAdapter);
360         mAdapter.add(getResources().getString(R.string.dsu_loader_loading));
361         new Thread(new Fetcher(url)).start();
362     }
363 
364     @Override
onListItemClick(ListView l, View v, int position, long id)365     protected void onListItemClick(ListView l, View v, int position, long id) {
366         Object selected = mAdapter.getItem(position);
367         if (selected instanceof DSUPackage) {
368             DSUPackage dsu = (DSUPackage) selected;
369             mAdapter.clear();
370             mAdapter.add(getResources().getString(R.string.dsu_loader_loading));
371             new Thread(new Runnable() {
372                 public void run() {
373                     String termsOfService = "";
374                     if (dsu.mTosUrl != null) {
375                         try {
376                             termsOfService = readAll(dsu.mTosUrl);
377                         } catch (IOException e) {
378                             Slog.e(TAG, e.toString());
379                         }
380                     }
381                     Intent intent = new Intent(DSULoader.this, DSUTermsOfServiceActivity.class);
382                     intent.putExtra(DSUTermsOfServiceActivity.KEY_TOS, termsOfService);
383                     intent.setData(Uri.parse(dsu.mUri.toString()));
384                     intent.putExtra("KEY_PUBKEY", dsu.mPubKey);
385                     startActivity(intent);
386                 }
387             }).start();
388         }
389         finish();
390     }
391 
392     private class DSUPackageListAdapter extends ArrayAdapter<Object> {
393         private final LayoutInflater mInflater;
394 
DSUPackageListAdapter(Context context)395         DSUPackageListAdapter(Context context) {
396             super(context, 0);
397             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
398         }
399 
400         @Override
getView(int position, View convertView, ViewGroup parent)401         public View getView(int position, View convertView, ViewGroup parent) {
402             AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
403             convertView = holder.rootView;
404             Object item = getItem(position);
405             if (item instanceof DSUPackage) {
406                 DSUPackage dsu = (DSUPackage) item;
407                 holder.appName.setText(dsu.mName);
408                 holder.summary.setText(dsu.mDetails);
409             } else {
410                 String msg = (String) item;
411                 holder.summary.setText(msg);
412             }
413             holder.appIcon.setImageDrawable(null);
414             holder.disabled.setVisibility(View.GONE);
415             return convertView;
416         }
417     }
418 }
419