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