package org.safermobile.intheclear.data; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import org.safermobile.intheclear.ITCConstants; import org.safermobile.intheclear.R; import org.safermobile.utils.FolderIterator; import android.accounts.Account; import android.accounts.AccountManager; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.RemoteException; import android.provider.CallLog; import android.provider.ContactsContract; import android.provider.ContactsContract.Data; import android.provider.MediaStore; import android.util.Log; public class PIMWiper extends Thread { private static ContentResolver cr; private static AccountManager am; private static Context c; private boolean contacts,photos,callLog,sms,calendar,sdcard; public static Bitmap gracie; public PIMWiper(Context c,boolean contacts,boolean photos,boolean callLog,boolean sms,boolean calendar,boolean sdcard) { this.contacts = contacts; this.photos = photos; this.callLog = callLog; this.sms = sms; this.calendar = calendar; this.sdcard = sdcard; try { gracie = BitmapFactory.decodeResource(c.getResources(), R.drawable.gracie_the_mixed_breed); } catch(Exception e) { Log.d(ITCConstants.Log.ITC,"gracie not found: " + e); } PIMWiper.c = c; PIMWiper.cr = c.getContentResolver(); PIMWiper.am = AccountManager.get(c); } @Override public void run() { try { // FIRST, you must turn off sync or else you get the "Deleted Contacts" error... preventSync(); if(contacts) { PIMWiper.wipeContacts(); PIMWiper.wipePhoneNumbers(); PIMWiper.wipeEmail(); } if(photos) { PIMWiper.wipePhotos(); PIMWiper.wipeVideos(); PIMWiper.wipeImageThumnbnails(); PIMWiper.wipeVideoThumbnails(); } if(callLog) { PIMWiper.wipeCallLog(); } if(sms) { PIMWiper.wipeSMS(); } if(calendar) { PIMWiper.wipeCalendar(); } if(sdcard) { new FolderIterator(); ArrayList<File> folders = FolderIterator.getFoldersOnSDCard(); Log.d(ITCConstants.Log.ITC,"Preparing to wipe " + folders.size() + " folders from the SD Card."); for(File f : folders) { PIMWiper.wipeFolder(f); } } } catch(IOException e){ Log.d(ITCConstants.Log.ITC,"Wipe has failed: " + e); } } private static void getAvailableColumns(Cursor cursor) { String[] availableColumns = cursor.getColumnNames(); StringBuffer sbb = new StringBuffer(); for(String s : availableColumns) { sbb.append(s + ", "); } Log.d(ITCConstants.Log.ITC,"Available Columns: " + sbb.toString()); } private static void preventSync() { Log.d(ITCConstants.Log.ITC,"accts: " + am.getAccounts()); Account[] accounts = am.getAccounts(); for(Account a : accounts) { ContentResolver.setIsSyncable(a, ContactsContract.AUTHORITY, 0); } } private static ArrayList<Integer> wipeAssets(Uri uriBase, String authority, String[] rewriteStrings, String[] rewriteFiles) throws FileNotFoundException, IOException { /* * primarily for the sake of the calendar wipe, * return the list of asset IDs, should you need to drill down further * into the content resolver */ ArrayList<Integer> assetIds = new ArrayList<Integer>(); Cursor cursor = null; long _id = 0; try { cursor = cr.query(uriBase, null, null, null, null); if(cursor != null && cursor.getCount() > 0) { getAvailableColumns(cursor); cursor.moveToFirst(); Log.d(ITCConstants.Log.ITC,"there are " + cursor.getCount() + " records in type: " + uriBase.toString()); while(!cursor.isAfterLast()) { _id = cursor.getLong(cursor.getColumnIndex("_id")); assetIds.add((int) _id); ArrayList<ContentProviderOperation> cpo = new ArrayList<ContentProviderOperation>(); if(rewriteStrings != null) { for(String s : rewriteStrings) { if(cursor.getString(cursor.getColumnIndex(s)) != null && cursor.getString(cursor.getColumnIndex(s)).length() > 0) { StringBuffer sb = new StringBuffer(); for(@SuppressWarnings("unused") char c : cursor.getString(cursor.getColumnIndex(s)).toCharArray()) { sb.append(0); } cpo.add(ContentProviderOperation .newUpdate(ContentUris.withAppendedId(uriBase, _id)) .withSelection(Data._ID + "=?", new String[] {Integer.toString((int) _id)}) .withValue(s,sb.toString()) .build() ); Log.d(ITCConstants.Log.ITC,"old " + s + " : " + cursor.getString(cursor.getColumnIndex(s)) + " | new " + s + " : " + sb.toString()); } } } if(rewriteFiles != null && rewriteFiles.length > 0) { for(String s : rewriteFiles) { if(cursor.getString(cursor.getColumnIndex(s)) != null && cursor.getString(cursor.getColumnIndex(s)).length() > 0) { // If the string contains a file path, zero-out/delete the referenced file as well File f = new File(cursor.getString(cursor.getColumnIndex(s))); if(f.isFile()) rewriteAndDelete(f); } } } // rewrite content if(!cpo.isEmpty()) { try { cr.applyBatch(authority, cpo); } catch (RemoteException e) { Log.d(ITCConstants.Log.ITC,"FAIL : " + e); } catch (OperationApplicationException e) { Log.d(ITCConstants.Log.ITC,"FAIL : " + e); } catch (UnsupportedOperationException e) { Log.d(ITCConstants.Log.ITC,"FAIL : " + e); } cpo.clear(); } // delete content //Log.d(ITCConstants.Log.ITC,"DELETE " + ContentUris.withAppendedId(uriBase, _id).toString() + " ?"); try { cr.delete(ContentUris.withAppendedId(uriBase, _id), null, null); } catch(UnsupportedOperationException e) { Log.d(ITCConstants.Log.ITC,"CAN\'T DELETE BECAUSE: " + e); // there's an exception, so try building the delete query from scratch and forcing it through? try { cr.delete(uriBase, Data._ID + "=?", new String[] {Integer.toString((int) _id)}); } catch(UnsupportedOperationException e2){} } cursor.moveToNext(); } } else { Log.d(ITCConstants.Log.ITC,"there are no records to manipulate here: " + uriBase.toString()); } cursor.close(); } catch(NullPointerException npe) {} return assetIds; } private static void rewriteAndDelete(final File f) throws FileNotFoundException, IOException { // first, determine if file is an image Log.d(ITCConstants.Log.ITC,"filename " + f.getName()); boolean isImageFile = false; try { String fileType = f.getName().substring(f.getName().length() - 4); if( fileType.compareTo(".jpg") == 0 || fileType.compareTo(".png") == 0 || fileType.compareTo(".gif") == 0) { isImageFile = true; } } catch(StringIndexOutOfBoundsException e) {} if(!isImageFile) { // if not an image... FileInputStream fis = new FileInputStream(f); FileWriter fw = new FileWriter(f,false); try { char[] newBytes = new char[(int) f.length()]; int offset = 0; while(fis.read() != -1) { newBytes[offset] = 0; offset++; } fis.close(); fw.write(newBytes); fw.close(); // delete the file f.delete(); } catch(OutOfMemoryError e) { Log.d(ITCConstants.Log.ITC,"VM limit reached in the zero-out process. Garbage collection prompted."); fis.close(); fw.close(); f.delete(); System.gc(); } } else { // rewrite as dummy bitmap FileOutputStream fos = new FileOutputStream(f); gracie.compress(Bitmap.CompressFormat.JPEG,30,fos); fos.flush(); fos.close(); // ...and force media scanner to rescan the file MediaScannerConnection.scanFile( PIMWiper.c, new String[] {f.getPath()}, new String[] {"JPEG"}, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { Log.d(ITCConstants.Log.ITC,"we have scanned in " + path + " with the media scanner!"); // delete the file f.delete(); } }); } } public static void wipeCalendar() throws FileNotFoundException, IOException { /* * TODO: this needs help. * getting error "IllegalArgumentException: * WHERE based updates not supported" * * however, we have finally pinpointed the correct * content authority and URI base */ Uri uriBase = Uri.parse("content://com.android.calendar/calendars"); //wipeAssets(uriBase,"com.android.calendar",ITCConstants.ContentTargets.CALENDAR.STRINGS,null); ArrayList<Integer> calIds = wipeAssets(uriBase,"com.android.calendar",null,null); for(long cal : calIds) { Log.d(ITCConstants.Log.ITC,"cal: " + cal); } } public static void wipeContacts() throws FileNotFoundException, IOException { Uri uriBase = ContactsContract.Contacts.CONTENT_URI; wipeAssets( uriBase,ContactsContract.AUTHORITY,ITCConstants.ContentTargets.CONTACT.STRINGS, null ); } public static void wipePhoneNumbers() throws FileNotFoundException, IOException { Uri uriBase = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; wipeAssets( uriBase,ContactsContract.AUTHORITY,ITCConstants.ContentTargets.PHONE_NUMBER.STRINGS, null ); } public static void wipeEmail() throws FileNotFoundException, IOException { Uri uriBase = ContactsContract.CommonDataKinds.Email.CONTENT_URI; wipeAssets( uriBase,ContactsContract.AUTHORITY,ITCConstants.ContentTargets.EMAIL.STRINGS, null ); } public static void wipePhotos() throws FileNotFoundException, IOException { Uri[] uriBases = { MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media.INTERNAL_CONTENT_URI }; for(Uri uriBase : uriBases) { wipeAssets( uriBase, MediaStore.AUTHORITY, ITCConstants.ContentTargets.IMAGE.STRINGS, ITCConstants.ContentTargets.IMAGE.FILES ); } } public static void wipeImageThumnbnails() throws FileNotFoundException, IOException { Uri[] uriBases = { MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, MediaStore.Images.Thumbnails.INTERNAL_CONTENT_URI }; for(Uri uriBase : uriBases) { wipeAssets( uriBase, MediaStore.AUTHORITY, ITCConstants.ContentTargets.IMAGE_THUMBNAIL.STRINGS, ITCConstants.ContentTargets.IMAGE_THUMBNAIL.FILES ); } } public static void wipeVideoThumbnails() throws FileNotFoundException, IOException { Uri[] uriBases = { android.provider.MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI, android.provider.MediaStore.Video.Thumbnails.INTERNAL_CONTENT_URI }; for(Uri uriBase : uriBases) { wipeAssets( uriBase, MediaStore.AUTHORITY, ITCConstants.ContentTargets.VIDEO_THUMBNAIL.STRINGS, ITCConstants.ContentTargets.VIDEO_THUMBNAIL.FILES ); } } public static void wipeVideos() throws FileNotFoundException, IOException { Uri[] uriBases = { android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI, android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI }; for(Uri uriBase : uriBases) { wipeAssets( uriBase, MediaStore.AUTHORITY, ITCConstants.ContentTargets.VIDEO.STRINGS, ITCConstants.ContentTargets.VIDEO.FILES ); } } public static void wipeSMS() throws FileNotFoundException, IOException { Uri uriBase = Uri.parse("content://sms"); wipeAssets( uriBase, "sms", ITCConstants.ContentTargets.SMS.STRINGS, null ); } public static void wipeCallLog() throws FileNotFoundException, IOException { Uri uriBase = android.provider.CallLog.Calls.CONTENT_URI; wipeAssets( uriBase, CallLog.AUTHORITY, ITCConstants.ContentTargets.CALL_LOG.STRINGS, null ); } private static ArrayList<File> getInnerFiles(File f) { ArrayList<File> innerFiles = new ArrayList<File>(); for(File file : f.listFiles()) { if(file.isFile() && file.canWrite()) innerFiles.add(file); else if(file.isDirectory() && file.canRead()) innerFiles.addAll(getInnerFiles(file)); } return innerFiles; } public static void wipeFolder(File folder) throws FileNotFoundException, IOException { ArrayList<File> del = new ArrayList<File>(); StringBuffer sb = new StringBuffer(); for(File file : folder.listFiles()) { if(file.isFile() && file.canWrite()) del.add(file); else if(file.isDirectory() && file.canRead()) del.addAll(getInnerFiles(file)); } int counter = 1; for(File f : del) { sb.append(counter++ + ". " + f.getPath() + "\n"); rewriteAndDelete(f); } Log.d(ITCConstants.Log.ITC,"FOLDER " + folder.getName() + " contained:\n" + sb.toString()); // remove this folder (if it's not the SDCard, naturally) // if, for some reason, the folder was not fully emptied, // this will fail. so do it again. /* if(folder.getPath().compareTo(FolderIterator.pathToSDCard.toString()) != 0) if(!folder.delete()) wipeFolder(folder); */ } }