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