DirListActivity.java revision ff314d7094fdc6a1869d6cbe17db16c5b9129009
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 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 */ 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; 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 89 private FileFilter mFileFilter; 90 91 /** 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 /** 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 150 return mRelativePath.equals(((ListItem)o).getRelativePath()); 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 179 TextView label = (TextView)row.findViewById(R.id.label); 180 label.setText(mItems[position].getName()); 181 182 ImageView icon = (ImageView)row.findViewById(R.id.icon); 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 197 ForwarderManager.getForwarderManager().start(); 198 199 mFileFilter = new FileFilter(ROOT_DIR_PATH); 200 mListView = getListView(); 201 202 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 203 @Override 204 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 205 ListItem item = (ListItem)parent.getItemAtPosition(position); 206 207 if (item.isDirectory()) { 208 showDir(item.getRelativePath()); 209 } else { 210 /** Run the test */ 211 runAllTestsUnder(item.getRelativePath()); 212 } 213 } 214 }); 215 216 mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { 217 @Override 218 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 219 ListItem item = (ListItem)parent.getItemAtPosition(position); 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 234 /** All the paths are relative to test root dir where possible */ 235 showDir(""); 236 } 237 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 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 289 @Override 290 protected Dialog onCreateDialog(int id, final Bundle args) { 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) { 305 removeDialog(DIALOG_RUN_ABORT_DIR); 306 runAllTestsUnder(args.getString("relativePath")); 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 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; 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(); 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. 397 * 398 * The dirPath is relative. 399 */ 400 private ListItem[] getDirList(String dirPath) { 401 List<ListItem> subDirs = new ArrayList<ListItem>(); 402 List<ListItem> subFiles = new ArrayList<ListItem>(); 403 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)); 408 } 409 } 410 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 } 417 418 /** Concatenate the two lists */ 419 subDirs.addAll(subFiles); 420 421 return subDirs.toArray(new ListItem[subDirs.size()]); 422 } 423}