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