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