1/*
2 * Copyright (C) 2015 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
17package com.android.preload;
18
19import com.android.ddmlib.Client;
20import com.android.ddmlib.IDevice;
21import com.android.preload.actions.ClearTableAction;
22import com.android.preload.actions.ComputeThresholdAction;
23import com.android.preload.actions.ComputeThresholdXAction;
24import com.android.preload.actions.DeviceSpecific;
25import com.android.preload.actions.ExportAction;
26import com.android.preload.actions.ImportAction;
27import com.android.preload.actions.ReloadListAction;
28import com.android.preload.actions.RunMonkeyAction;
29import com.android.preload.actions.ScanAllPackagesAction;
30import com.android.preload.actions.ScanPackageAction;
31import com.android.preload.actions.ShowDataAction;
32import com.android.preload.actions.WritePreloadedClassesAction;
33import com.android.preload.classdataretrieval.ClassDataRetriever;
34import com.android.preload.classdataretrieval.hprof.Hprof;
35import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
36import com.android.preload.ui.IUI;
37import com.android.preload.ui.SequenceUI;
38import com.android.preload.ui.SwingUI;
39
40import java.io.File;
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.Collection;
44import java.util.Iterator;
45import java.util.List;
46import java.util.Map;
47import java.util.NoSuchElementException;
48
49import javax.swing.Action;
50import javax.swing.DefaultListModel;
51
52public class Main {
53
54    /**
55     * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is
56     * off for now.
57     */
58    public final static boolean ENABLE_TRACING = false;
59
60    /**
61     * Ten-second timeout.
62     */
63    public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
64
65    /**
66     * Hprof timeout. Two minutes.
67     */
68    public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000;
69
70    private IDevice device;
71    private static ClientUtils clientUtils;
72
73    private DumpTableModel dataTableModel;
74    private DefaultListModel<Client> clientListModel;
75
76    private IUI ui;
77
78    // Actions that need to be updated once a device is selected.
79    private Collection<DeviceSpecific> deviceSpecificActions;
80
81    // Current main instance.
82    private static Main top;
83    private static boolean useJdwpClassDataRetriever = false;
84
85    public final static String CLASS_PRELOAD_BLACKLIST = "android.app.AlarmManager$" + "|"
86            + "android.app.SearchManager$" + "|" + "android.os.FileObserver$" + "|"
87            + "com.android.server.PackageManagerService\\$AppDirObserver$" + "|" +
88
89
90            // Threads
91            "android.os.AsyncTask$" + "|" + "android.pim.ContactsAsyncHelper$" + "|"
92            + "android.webkit.WebViewClassic\\$1$" + "|" + "java.lang.ProcessManager$" + "|"
93            + "(.*\\$NoPreloadHolder$)";
94
95    public final static String SCAN_ALL_CMD = "scan-all";
96    public final static String SCAN_PACKAGE_CMD = "scan";
97    public final static String COMPUTE_FILE_CMD = "comp";
98    public final static String EXPORT_CMD = "export";
99    public final static String IMPORT_CMD = "import";
100    public final static String WRITE_CMD = "write";
101
102    /**
103     * @param args
104     */
105    public static void main(String[] args) {
106        Main m;
107        if (args.length > 0 && args[0].equals("--seq")) {
108            m = createSequencedMain(args);
109        } else {
110            m = new Main(new SwingUI());
111        }
112
113        top = m;
114        m.startUp();
115    }
116
117    public Main(IUI ui) {
118        this.ui = ui;
119
120        clientListModel = new DefaultListModel<Client>();
121        dataTableModel = new DumpTableModel();
122
123        clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS);  // Client utils with 10s timeout.
124
125        List<Action> actions = new ArrayList<Action>();
126        actions.add(new ReloadListAction(clientUtils, null, clientListModel));
127        actions.add(new ClearTableAction(dataTableModel));
128        actions.add(new RunMonkeyAction(null, dataTableModel));
129        actions.add(new ScanPackageAction(clientUtils, null, dataTableModel));
130        actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel));
131        actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2,
132                CLASS_PRELOAD_BLACKLIST));
133        actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1,
134                null));
135        actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel,
136                CLASS_PRELOAD_BLACKLIST));
137        actions.add(new WritePreloadedClassesAction(clientUtils, null, dataTableModel));
138        actions.add(new ShowDataAction(dataTableModel));
139        actions.add(new ImportAction(dataTableModel));
140        actions.add(new ExportAction(dataTableModel));
141
142        deviceSpecificActions = new ArrayList<DeviceSpecific>();
143        for (Action a : actions) {
144            if (a instanceof DeviceSpecific) {
145                deviceSpecificActions.add((DeviceSpecific)a);
146            }
147        }
148
149        ui.prepare(clientListModel, dataTableModel, actions);
150    }
151
152    /**
153     * @param args
154     * @return
155     */
156    private static Main createSequencedMain(String[] args) {
157        SequenceUI ui = new SequenceUI();
158        Main main = new Main(ui);
159
160        Iterator<String> it = Arrays.asList(args).iterator();
161        it.next();  // --seq
162        // Setup
163        ui.choice("#" + it.next());  // Device.
164        ui.confirmNo();              // Prepare: no.
165        // Actions
166        try {
167            while (it.hasNext()) {
168                String op = it.next();
169                // Operation: Scan a single package
170                if (SCAN_PACKAGE_CMD.equals(op)) {
171                    System.out.println("Scanning package.");
172                    ui.action(ScanPackageAction.class);
173                    ui.client(it.next());
174                // Operation: Scan all packages
175                } else if (SCAN_ALL_CMD.equals(op)) {
176                    System.out.println("Scanning all packages.");
177                    ui.action(ScanAllPackagesAction.class);
178                // Operation: Export the output to a file
179                } else if (EXPORT_CMD.equals(op)) {
180                    System.out.println("Exporting data.");
181                    ui.action(ExportAction.class);
182                    ui.output(new File(it.next()));
183                // Operation: Import the input from a file or directory
184                } else if (IMPORT_CMD.equals(op)) {
185                    System.out.println("Importing data.");
186                    File file = new File(it.next());
187                    if (!file.exists()) {
188                        throw new RuntimeException(
189                                String.format("File does not exist, %s.", file.getAbsolutePath()));
190                    } else if (file.isFile()) {
191                        ui.action(ImportAction.class);
192                        ui.input(file);
193                    } else if (file.isDirectory()) {
194                        for (File content : file.listFiles()) {
195                            ui.action(ImportAction.class);
196                            ui.input(content);
197                        }
198                    }
199                // Operation: Compute preloaded classes with specific threshold
200                } else if (COMPUTE_FILE_CMD.equals(op)) {
201                    System.out.println("Compute preloaded classes.");
202                    ui.action(ComputeThresholdXAction.class);
203                    ui.input(it.next());
204                    ui.confirmYes();
205                    ui.output(new File(it.next()));
206                // Operation: Write preloaded classes from a specific file
207                } else if (WRITE_CMD.equals(op)) {
208                    System.out.println("Writing preloaded classes.");
209                    ui.action(WritePreloadedClassesAction.class);
210                    ui.input(new File(it.next()));
211                }
212            }
213        } catch (NoSuchElementException e) {
214            System.out.println("Failed to parse action sequence correctly.");
215            throw e;
216        }
217
218        return main;
219    }
220
221    public static IUI getUI() {
222        return top.ui;
223    }
224
225    public static ClassDataRetriever getClassDataRetriever() {
226        if (useJdwpClassDataRetriever) {
227            return new JDWPClassDataRetriever();
228        } else {
229            return new Hprof(HPROF_TIMEOUT_MILLIS);
230        }
231    }
232
233    public IDevice getDevice() {
234        return device;
235    }
236
237    public void setDevice(IDevice device) {
238        this.device = device;
239        for (DeviceSpecific ds : deviceSpecificActions) {
240            ds.setDevice(device);
241        }
242    }
243
244    public DefaultListModel<Client> getClientListModel() {
245        return clientListModel;
246    }
247
248    static class DeviceWrapper {
249        IDevice device;
250
251        public DeviceWrapper(IDevice d) {
252            device = d;
253        }
254
255        @Override
256        public String toString() {
257            return device.getName() + " (#" + device.getSerialNumber() + ")";
258        }
259    }
260
261    private void startUp() {
262        getUI().showWaitDialog();
263        initDevice();
264
265        // Load clients.
266        new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
267
268        getUI().hideWaitDialog();
269        getUI().ready();
270    }
271
272    private void initDevice() {
273        DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS);
274
275        IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS);
276        if (devices == null || devices.length == 0) {
277            throw new RuntimeException("Could not find any devices...");
278        }
279
280        getUI().hideWaitDialog();
281
282        DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length];
283        for (int i = 0; i < devices.length; i++) {
284            deviceWrappers[i] = new DeviceWrapper(devices[i]);
285        }
286
287        DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device",
288                deviceWrappers);
289        if (ret != null) {
290            setDevice(ret.device);
291        } else {
292            System.exit(0);
293        }
294
295        boolean prepare = Main.getUI().showConfirmDialog("Prepare device?",
296                "Do you want to prepare the device? This is highly recommended.");
297        if (prepare) {
298            String buildType = DeviceUtils.getBuildType(device);
299            if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) {
300                Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType
301                        + ")");
302                return;
303            }
304            if (DeviceUtils.hasPrebuiltBootImage(device)) {
305                Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot "
306                        + "image!");
307                return;
308            }
309
310            if (ENABLE_TRACING) {
311                DeviceUtils.enableTracing(device);
312            }
313
314            Main.getUI().showMessageDialog("The device will reboot. This will potentially take a "
315                    + "long time. Please be patient.");
316            boolean success = false;
317            try {
318                success = DeviceUtils.overwritePreloaded(device, null, 15 * 60);
319            } catch (Exception e) {
320                System.err.println(e);
321            } finally {
322                if (!success) {
323                    Main.getUI().showMessageDialog(
324                            "Removing preloaded-classes failed unexpectedly!");
325                }
326            }
327        }
328    }
329
330    public static Map<String, String> findAndGetClassData(IDevice device, String packageName)
331            throws Exception {
332        Client client = clientUtils.findClient(device, packageName, -1);
333        if (client == null) {
334            throw new RuntimeException("Could not find client...");
335        }
336        System.out.println("Found client: " + client);
337
338        return getClassDataRetriever().getClassData(client);
339    }
340
341}
342