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.settings.deviceinfo; 18 19 import android.app.Activity; 20 import android.app.ListActivity; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.os.storage.StorageVolume; 24 import android.text.format.Formatter; 25 import android.util.Log; 26 import android.util.SparseBooleanArray; 27 import android.view.ActionMode; 28 import android.view.LayoutInflater; 29 import android.view.Menu; 30 import android.view.MenuInflater; 31 import android.view.MenuItem; 32 import android.view.View; 33 import android.view.View.OnClickListener; 34 import android.view.View.OnLongClickListener; 35 import android.view.ViewGroup; 36 import android.widget.BaseAdapter; 37 import android.widget.CompoundButton; 38 import android.widget.CompoundButton.OnCheckedChangeListener; 39 import android.widget.ListView; 40 41 import com.android.settings.R; 42 import com.android.settings.deviceinfo.StorageMeasurement.FileInfo; 43 44 import java.io.File; 45 import java.util.ArrayList; 46 import java.util.List; 47 48 /** 49 * This class handles the selection and removal of Misc files. 50 */ 51 public class MiscFilesHandler extends ListActivity { 52 private static final String TAG = "MemorySettings"; 53 private String mNumSelectedFormat; 54 private String mNumBytesSelectedFormat; 55 private MemoryMearurementAdapter mAdapter; 56 private LayoutInflater mInflater; 57 58 @Override 59 public void onCreate(Bundle savedInstanceState) { 60 super.onCreate(savedInstanceState); 61 setFinishOnTouchOutside(true); 62 setTitle(R.string.misc_files); 63 mNumSelectedFormat = getString(R.string.misc_files_selected_count); 64 mNumBytesSelectedFormat = getString(R.string.misc_files_selected_count_bytes); 65 mAdapter = new MemoryMearurementAdapter(this); 66 mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); 67 setContentView(R.layout.settings_storage_miscfiles_list); 68 ListView lv = getListView(); 69 lv.setItemsCanFocus(true); 70 lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); 71 lv.setMultiChoiceModeListener(new ModeCallback(this)); 72 setListAdapter(mAdapter); 73 } 74 75 private class ModeCallback implements ListView.MultiChoiceModeListener { 76 private int mDataCount; 77 private final Context mContext; 78 79 public ModeCallback(Context context) { 80 mContext = context; 81 mDataCount = mAdapter.getCount(); 82 } 83 84 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 85 final MenuInflater inflater = getMenuInflater(); 86 inflater.inflate(R.menu.misc_files_menu, menu); 87 return true; 88 } 89 90 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 91 return true; 92 } 93 94 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 95 ListView lv = getListView(); 96 switch (item.getItemId()) { 97 case R.id.action_delete: 98 // delete the files selected 99 SparseBooleanArray checkedItems = lv.getCheckedItemPositions(); 100 int checkedCount = getListView().getCheckedItemCount(); 101 if (checkedCount > mDataCount) { 102 throw new IllegalStateException("checked item counts do not match. " + 103 "checkedCount: " + checkedCount + ", dataSize: " + mDataCount); 104 } 105 if (mDataCount > 0) { 106 ArrayList<Object> toRemove = new ArrayList<Object>(); 107 for (int i = 0; i < mDataCount; i++) { 108 if (!checkedItems.get(i)) { 109 //item not selected 110 continue; 111 } 112 if (StorageMeasurement.LOGV) { 113 Log.i(TAG, "deleting: " + mAdapter.getItem(i)); 114 } 115 // delete the file 116 File file = new File(mAdapter.getItem(i).mFileName); 117 if (file.isDirectory()) { 118 deleteDir(file); 119 } else { 120 file.delete(); 121 } 122 toRemove.add(mAdapter.getItem(i)); 123 } 124 mAdapter.removeAll(toRemove); 125 mAdapter.notifyDataSetChanged(); 126 mDataCount = mAdapter.getCount(); 127 } 128 mode.finish(); 129 break; 130 131 case R.id.action_select_all: 132 // check ALL items 133 for (int i = 0; i < mDataCount; i++) { 134 lv.setItemChecked(i, true); 135 } 136 // update the title and subtitle with number selected and numberBytes selected 137 onItemCheckedStateChanged(mode, 1, 0, true); 138 break; 139 } 140 return true; 141 } 142 143 // Deletes all files and subdirectories under given dir. 144 // Returns true if all deletions were successful. 145 // If a deletion fails, the method stops attempting to delete and returns false. 146 private boolean deleteDir(File dir) { 147 String[] children = dir.list(); 148 if (children != null) { 149 for (int i=0; i < children.length; i++) { 150 boolean success = deleteDir(new File(dir, children[i])); 151 if (!success) { 152 return false; 153 } 154 } 155 } 156 // The directory is now empty so delete it 157 return dir.delete(); 158 } 159 160 public void onDestroyActionMode(ActionMode mode) { 161 // This block intentionally left blank 162 } 163 164 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, 165 boolean checked) { 166 ListView lv = getListView(); 167 int numChecked = lv.getCheckedItemCount(); 168 mode.setTitle(String.format(mNumSelectedFormat, numChecked, mAdapter.getCount())); 169 170 // total the sizes of all items selected so far 171 SparseBooleanArray checkedItems = lv.getCheckedItemPositions(); 172 long selectedDataSize = 0; 173 if (numChecked > 0) { 174 for (int i = 0; i < mDataCount; i++) { 175 if (checkedItems.get(i)) { 176 // item is checked 177 selectedDataSize += mAdapter.getItem(i).mSize; 178 } 179 } 180 } 181 mode.setSubtitle(String.format(mNumBytesSelectedFormat, 182 Formatter.formatFileSize(mContext, selectedDataSize), 183 Formatter.formatFileSize(mContext, mAdapter.getDataSize()))); 184 } 185 } 186 187 class MemoryMearurementAdapter extends BaseAdapter { 188 private ArrayList<StorageMeasurement.FileInfo> mData = null; 189 private long mDataSize = 0; 190 private Context mContext; 191 192 public MemoryMearurementAdapter(Activity activity) { 193 mContext = activity; 194 final StorageVolume storageVolume = activity.getIntent().getParcelableExtra( 195 StorageVolume.EXTRA_STORAGE_VOLUME); 196 StorageMeasurement mMeasurement = StorageMeasurement.getInstance( 197 activity, storageVolume); 198 if (mMeasurement == null) return; 199 mData = (ArrayList<StorageMeasurement.FileInfo>) mMeasurement.mFileInfoForMisc; 200 if (mData != null) { 201 for (StorageMeasurement.FileInfo info : mData) { 202 mDataSize += info.mSize; 203 } 204 } 205 } 206 207 @Override 208 public int getCount() { 209 return (mData == null) ? 0 : mData.size(); 210 } 211 212 @Override 213 public StorageMeasurement.FileInfo getItem(int position) { 214 if (mData == null || mData.size() <= position) { 215 return null; 216 } 217 return mData.get(position); 218 } 219 220 @Override 221 public long getItemId(int position) { 222 if (mData == null || mData.size() <= position) { 223 return 0; 224 } 225 return mData.get(position).mId; 226 } 227 228 public void removeAll(List<Object> objs) { 229 if (mData == null) { 230 return; 231 } 232 for (Object o : objs) { 233 mData.remove(o); 234 mDataSize -= ((StorageMeasurement.FileInfo) o).mSize; 235 } 236 } 237 238 public long getDataSize() { 239 return mDataSize; 240 } 241 242 @Override 243 public void notifyDataSetChanged() { 244 super.notifyDataSetChanged(); 245 } 246 247 @Override 248 public View getView(int position, View convertView, ViewGroup parent) { 249 final FileItemInfoLayout view = (convertView == null) ? 250 (FileItemInfoLayout) mInflater.inflate(R.layout.settings_storage_miscfiles, 251 parent, false) : (FileItemInfoLayout) convertView; 252 FileInfo item = getItem(position); 253 view.setFileName(item.mFileName); 254 view.setFileSize(Formatter.formatFileSize(mContext, item.mSize)); 255 final ListView listView = (ListView) parent; 256 final int listPosition = position; 257 view.getCheckBox().setOnCheckedChangeListener(new OnCheckedChangeListener() { 258 259 @Override 260 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 261 listView.setItemChecked(listPosition, isChecked); 262 } 263 264 }); 265 view.setOnLongClickListener(new OnLongClickListener() { 266 @Override 267 public boolean onLongClick(View v) { 268 if (listView.getCheckedItemCount() > 0) { 269 return false; 270 } 271 listView.setItemChecked(listPosition, !view.isChecked()); 272 return true; 273 } 274 }); 275 view.setOnClickListener(new OnClickListener() { 276 @Override 277 public void onClick(View v) { 278 if (listView.getCheckedItemCount() > 0) { 279 listView.setItemChecked(listPosition, !view.isChecked()); 280 } 281 } 282 }); 283 return view; 284 } 285 } 286 } 287