DirListActivity.java revision c54df24fa9bfd0a2b53404139e7914e68f192af4
1/*
2 * Copyright (C) 2010 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.dumprendertree2.ui;
18
19import com.android.dumprendertree2.FileFilter;
20import com.android.dumprendertree2.FsUtils;
21import com.android.dumprendertree2.TestsListActivity;
22import com.android.dumprendertree2.R;
23import com.android.dumprendertree2.forwarder.ForwarderManager;
24
25import android.app.Activity;
26import android.app.AlertDialog;
27import android.app.Dialog;
28import android.app.ListActivity;
29import android.app.ProgressDialog;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.content.res.Configuration;
33import android.os.Bundle;
34import android.os.Environment;
35import android.os.Handler;
36import android.os.Message;
37import android.view.LayoutInflater;
38import android.view.Menu;
39import android.view.MenuInflater;
40import android.view.MenuItem;
41import android.view.View;
42import android.view.ViewGroup;
43import android.widget.AdapterView;
44import android.widget.ArrayAdapter;
45import android.widget.ImageView;
46import android.widget.ListView;
47import android.widget.TextView;
48
49import java.io.File;
50import java.util.ArrayList;
51import java.util.List;
52
53/**
54 * An Activity that allows navigating through tests folders and choosing folders or tests to run.
55 */
56public class DirListActivity extends ListActivity {
57
58    private static final String LOG_TAG = "DirListActivity";
59
60    /** TODO: This is just a guess - think of a better way to achieve it */
61    private static final int MEAN_TITLE_CHAR_SIZE = 13;
62
63    private static final int PROGRESS_DIALOG_DELAY_MS = 200;
64
65    /** Code for the dialog, used in showDialog and onCreateDialog */
66    private static final int DIALOG_RUN_ABORT_DIR = 0;
67
68    /** Messages codes */
69    private static final int MSG_LOADED_ITEMS = 0;
70    private static final int MSG_SHOW_PROGRESS_DIALOG = 1;
71
72    /** Initialized lazily before first sProgressDialog.show() */
73    private static ProgressDialog sProgressDialog;
74
75    private ListView mListView;
76
77    /** This is a relative path! */
78    private String mCurrentDirPath;
79
80    /**
81     * A thread responsible for loading the contents of the directory from sd card
82     * and sending them via Message to main thread that then loads them into
83     * ListView
84     */
85    private class LoadListItemsThread extends Thread {
86        private Handler mHandler;
87        private String mRelativePath;
88
89        public LoadListItemsThread(String relativePath, Handler handler) {
90            mRelativePath = relativePath;
91            mHandler = handler;
92        }
93
94        @Override
95        public void run() {
96            Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS);
97            msg.obj = getDirList(mRelativePath);
98            mHandler.sendMessage(msg);
99        }
100    }
101
102    /**
103     * Very simple object to use inside ListView as an item.
104     */
105    private static class ListItem implements Comparable<ListItem> {
106        private String mRelativePath;
107        private String mName;
108        private boolean mIsDirectory;
109
110        public ListItem(String relativePath, boolean isDirectory) {
111            mRelativePath = relativePath;
112            mName = new File(relativePath).getName();
113            mIsDirectory = isDirectory;
114        }
115
116        public boolean isDirectory() {
117            return mIsDirectory;
118        }
119
120        public String getRelativePath() {
121            return mRelativePath;
122        }
123
124        public String getName() {
125            return mName;
126        }
127
128        @Override
129        public int compareTo(ListItem another) {
130            return mRelativePath.compareTo(another.getRelativePath());
131        }
132
133        @Override
134        public boolean equals(Object o) {
135            if (!(o instanceof ListItem)) {
136                return false;
137            }
138
139            return mRelativePath.equals(((ListItem)o).getRelativePath());
140        }
141
142        @Override
143        public int hashCode() {
144            return mRelativePath.hashCode();
145        }
146
147    }
148
149    /**
150     * A custom adapter that sets the proper icon and label in the list view.
151     */
152    private static class DirListAdapter extends ArrayAdapter<ListItem> {
153        private Activity mContext;
154        private ListItem[] mItems;
155
156        public DirListAdapter(Activity context, ListItem[] items) {
157            super(context, R.layout.dirlist_row, items);
158
159            mContext = context;
160            mItems = items;
161        }
162
163        @Override
164        public View getView(int position, View convertView, ViewGroup parent) {
165            LayoutInflater inflater = mContext.getLayoutInflater();
166            View row = inflater.inflate(R.layout.dirlist_row, null);
167
168            TextView label = (TextView)row.findViewById(R.id.label);
169            label.setText(mItems[position].getName());
170
171            ImageView icon = (ImageView)row.findViewById(R.id.icon);
172            if (mItems[position].isDirectory()) {
173                icon.setImageResource(R.drawable.folder);
174            } else {
175                icon.setImageResource(R.drawable.runtest);
176            }
177
178            return row;
179        }
180    }
181
182    @Override
183    protected void onCreate(Bundle savedInstanceState) {
184        super.onCreate(savedInstanceState);
185
186        ForwarderManager.getForwarderManager().start();
187
188        mListView = getListView();
189
190        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
191            @Override
192            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
193                ListItem item = (ListItem)parent.getItemAtPosition(position);
194
195                if (item.isDirectory()) {
196                    showDir(item.getRelativePath());
197                } else {
198                    /** Run the test */
199                    runAllTestsUnder(item.getRelativePath());
200                }
201            }
202        });
203
204        mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
205            @Override
206            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
207                ListItem item = (ListItem)parent.getItemAtPosition(position);
208
209                if (item.isDirectory()) {
210                    Bundle arguments = new Bundle(1);
211                    arguments.putString("name", item.getName());
212                    arguments.putString("relativePath", item.getRelativePath());
213                    showDialog(DIALOG_RUN_ABORT_DIR, arguments);
214                } else {
215                    /** TODO: Maybe show some info about a test? */
216                }
217
218                return true;
219            }
220        });
221
222        /** All the paths are relative to test root dir where possible */
223        showDir("");
224    }
225
226    private void runAllTestsUnder(String relativePath) {
227        Intent intent = new Intent();
228        intent.setClass(DirListActivity.this, TestsListActivity.class);
229        intent.setAction(Intent.ACTION_RUN);
230        intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
231        startActivity(intent);
232    }
233
234    @Override
235    public boolean onCreateOptionsMenu(Menu menu) {
236        MenuInflater inflater = getMenuInflater();
237        inflater.inflate(R.menu.gui_menu, menu);
238        return true;
239    }
240
241    @Override
242    public boolean onOptionsItemSelected(MenuItem item) {
243        switch (item.getItemId()) {
244            case R.id.run_all:
245                runAllTestsUnder(mCurrentDirPath);
246                return true;
247            default:
248                return super.onOptionsItemSelected(item);
249        }
250    }
251
252    @Override
253    /**
254     * Moves to the parent directory if one exists. Does not allow to move above
255     * the test 'root' directory.
256     */
257    public void onBackPressed() {
258        File currentDirParent = new File(mCurrentDirPath).getParentFile();
259        if (currentDirParent != null) {
260            showDir(currentDirParent.getPath());
261        } else {
262            showDir("");
263        }
264    }
265
266    /**
267     * Prevents the activity from recreating on change of orientation. The title needs to
268     * be recalculated.
269     */
270    @Override
271    public void onConfigurationChanged(Configuration newConfig) {
272        super.onConfigurationChanged(newConfig);
273
274        setTitle(shortenTitle(mCurrentDirPath));
275    }
276
277    @Override
278    protected Dialog onCreateDialog(int id, final Bundle args) {
279        Dialog dialog = null;
280        AlertDialog.Builder builder = new AlertDialog.Builder(this);
281
282        switch (id) {
283            case DIALOG_RUN_ABORT_DIR:
284                builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " +
285                        args.getString("name"));
286                builder.setMessage(R.string.dialog_run_abort_dir_msg);
287                builder.setCancelable(true);
288
289                builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button,
290                        new DialogInterface.OnClickListener() {
291                    @Override
292                    public void onClick(DialogInterface dialog, int which) {
293                        removeDialog(DIALOG_RUN_ABORT_DIR);
294                        runAllTestsUnder(args.getString("relativePath"));
295                    }
296                });
297
298                builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button,
299                        new DialogInterface.OnClickListener() {
300                    @Override
301                    public void onClick(DialogInterface dialog, int which) {
302                        removeDialog(DIALOG_RUN_ABORT_DIR);
303                    }
304                });
305
306                dialog = builder.create();
307                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
308                    @Override
309                    public void onCancel(DialogInterface dialog) {
310                        removeDialog(DIALOG_RUN_ABORT_DIR);
311                    }
312                });
313                break;
314        }
315
316        return dialog;
317    }
318
319    /**
320     * Loads the contents of dir into the list view.
321     *
322     * @param dirPath
323     *      directory to load into list view
324     */
325    private void showDir(String dirPath) {
326        mCurrentDirPath = dirPath;
327
328        /** Show progress dialog with a delay */
329        final Handler delayedDialogHandler = new Handler() {
330            @Override
331            public void handleMessage(Message msg) {
332                if (msg.what == MSG_SHOW_PROGRESS_DIALOG) {
333                    if (sProgressDialog == null) {
334                        sProgressDialog = new ProgressDialog(DirListActivity.this);
335                        sProgressDialog.setCancelable(false);
336                        sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
337                        sProgressDialog.setTitle(R.string.dialog_progress_title);
338                        sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
339                    }
340                    sProgressDialog.show();
341                }
342            }
343        };
344        Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG);
345        delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS);
346
347        /** Delegate loading contents from SD card to a new thread */
348        new LoadListItemsThread(mCurrentDirPath, new Handler() {
349            @Override
350            public void handleMessage(Message msg) {
351                if (msg.what == MSG_LOADED_ITEMS) {
352                    setListAdapter(new DirListAdapter(DirListActivity.this,
353                            (ListItem[])msg.obj));
354                    delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
355                    setTitle(shortenTitle(mCurrentDirPath));
356                    if (sProgressDialog != null) {
357                        sProgressDialog.dismiss();
358                    }
359                }
360            }
361        }).start();
362    }
363
364    /**
365     * TODO: find a neat way to determine number of characters that fit in the title
366     * bar.
367     * */
368    private String shortenTitle(String title) {
369        if (title.equals("")) {
370            return "Tests' root dir:";
371        }
372        int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE;
373
374        if (title.length() > charCount) {
375            return "..." + title.substring(title.length() - charCount);
376        } else {
377            return title;
378        }
379    }
380
381    /**
382     * Return the array with contents of the given directory.
383     * First it contains the subfolders, then the files. Both sorted
384     * alphabetically.
385     *
386     * The dirPath is relative.
387     */
388    private ListItem[] getDirList(String dirPath) {
389        List<ListItem> subDirs = new ArrayList<ListItem>();
390        List<ListItem> subFiles = new ArrayList<ListItem>();
391
392        for (String dirRelativePath : FsUtils.getLayoutTestsDirContents(dirPath, false,
393                true)) {
394            if (FileFilter.isTestDir(new File(dirRelativePath).getName())) {
395                subDirs.add(new ListItem(dirRelativePath, true));
396            }
397        }
398
399        for (String testRelativePath : FsUtils.getLayoutTestsDirContents(dirPath, false,
400                false)) {
401            if (FileFilter.isTestFile(new File(testRelativePath).getName())) {
402                subFiles.add(new ListItem(testRelativePath, false));
403            }
404        }
405
406        /** Concatenate the two lists */
407        subDirs.addAll(subFiles);
408
409        return subDirs.toArray(new ListItem[subDirs.size()]);
410    }
411}
412