-
-
Save alphamu/87ca7bf57b5f9e6a0f84db93bef3b792 to your computer and use it in GitHub Desktop.
Encrypted persisted and cached Parcelable data disklrucache and facebook conceal on Android.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.content.pm.PackageInfo; | |
import android.content.pm.PackageManager; | |
import android.os.Build; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.text.TextUtils; | |
import com.facebook.android.crypto.keychain.SharedPrefsBackedKeyChain; | |
import com.facebook.crypto.Crypto; | |
import com.facebook.crypto.Entity; | |
import com.facebook.crypto.util.SystemNativeCryptoLibrary; | |
import com.jakewharton.disklrucache.DiskLruCache; | |
import java.io.ByteArrayOutputStream; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.concurrent.Executor; | |
import java.util.concurrent.Executors; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
public abstract class AbstractParcelDiskCache implements DiskCache<Parcelable> { | |
private static final String LIST = "list"; | |
private static final String PARCELABLE = "parcelable"; | |
private static final String VALIDATE_KEY_REGEX = "[a-z0-9_-]{1,5}"; | |
private static final int MAX_KEY_SYMBOLS = 62; | |
private DiskLruCache cache; | |
private Executor storeExecutor; | |
private boolean saveInUI = true; | |
private static Crypto crypto; | |
/** | |
* @param context Context | |
* @param name The name of the sub-directory under the cache/store directory. | |
* @param maxSize in bytes | |
* @throws IOException | |
*/ | |
AbstractParcelDiskCache(Context context, String name, long maxSize) throws IOException { | |
storeExecutor = Executors.newSingleThreadExecutor(); | |
File dir = new File(getCacheDirectory(context), name); | |
int version = getVersionCode(context) + Build.VERSION.SDK_INT; | |
this.cache = DiskLruCache.open(dir, version, 1, maxSize); | |
if (crypto == null) { | |
crypto = new Crypto( | |
new SharedPrefsBackedKeyChain(context.getApplicationContext()), | |
new SystemNativeCryptoLibrary()); | |
} | |
} | |
abstract File getCacheDirectory(Context context); | |
// public static AbstractParcelDiskCache open(Context context, String name, long maxSize) throws IOException { | |
// return new AbstractParcelDiskCache(context, name, maxSize); | |
// } | |
public void set(String key, Parcelable value) { | |
key = validateKey(key); | |
Parcel parcel = Parcel.obtain(); | |
parcel.writeString(PARCELABLE); | |
parcel.writeParcelable(value, 0); | |
if (saveInUI) { | |
saveValue(cache, parcel, key); | |
} else { | |
storeExecutor.execute(new StoreParcelableValueTask(cache, parcel, key)); | |
} | |
} | |
public void set(String key, List<Parcelable> values) { | |
key = validateKey(key); | |
Parcel parcel = Parcel.obtain(); | |
parcel.writeString(LIST); | |
parcel.writeList(values); | |
if (saveInUI) { | |
saveValue(cache, parcel, key); | |
} else { | |
storeExecutor.execute(new StoreParcelableValueTask(cache, parcel, key)); | |
} | |
} | |
@Override | |
public <T> T get(String key, Class<T> clazz) { | |
key = validateKey(key); | |
Parcel parcel = getParcel(key); | |
if (parcel != null) { | |
try { | |
String type = parcel.readString(); | |
if (type.equals(LIST)) { | |
throw new IllegalAccessError("get list data with getList method"); | |
} | |
if (!type.equals(PARCELABLE)) { | |
throw new IllegalAccessError("Parcel doesn't contain parcelable data"); | |
} | |
return parcel.readParcelable(clazz.getClassLoader()); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
parcel.recycle(); | |
} | |
} | |
return null; | |
} | |
private Parcel getParcel(String key) { | |
key = validateKey(key); | |
byte[] value; | |
DiskLruCache.Snapshot snapshot = null; | |
try { | |
snapshot = cache.get(key); | |
if (snapshot == null) { | |
return null; | |
} | |
value = getBytesFromStream(snapshot.getInputStream(0)); | |
Parcel parcel = Parcel.obtain(); | |
parcel.unmarshall(value, 0, value.length); | |
parcel.setDataPosition(0); | |
return parcel; | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
if (snapshot != null) { | |
snapshot.close(); | |
} | |
} | |
return null; | |
} | |
private String validateKey(String key) { | |
Matcher keyMatcher = getPattern(VALIDATE_KEY_REGEX).matcher(key); | |
StringBuilder newKey = new StringBuilder(); | |
while (keyMatcher.find()) { | |
String group = keyMatcher.group(); | |
if (newKey.length() + group.length() > MAX_KEY_SYMBOLS) { | |
break; | |
} | |
newKey.append(group); | |
} | |
return newKey.toString().toLowerCase(); | |
} | |
public Pattern getPattern(String bodyRegex) { | |
int flags = Pattern.MULTILINE | Pattern.DOTALL | Pattern.CASE_INSENSITIVE; | |
return Pattern.compile(bodyRegex, flags); | |
} | |
public <T> List<T> getList(String key, Class itemClass) { | |
key = validateKey(key); | |
ArrayList<T> res = new ArrayList<>(); | |
Parcel parcel = getParcel(key); | |
if (parcel != null) { | |
try { | |
String type = parcel.readString(); | |
if (type.equals(PARCELABLE)) { | |
throw new IllegalAccessError("Get not a list data with get method"); | |
} | |
if (!type.equals(LIST)) { | |
throw new IllegalAccessError("Parcel doesn't contain list data"); | |
} | |
parcel.readList(res, itemClass != null ? itemClass.getClassLoader() : ArrayList.class.getClassLoader()); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
parcel.recycle(); | |
} | |
} | |
return res; | |
} | |
@SuppressWarnings("unused") | |
public <T> List<T> getList(String key) { | |
return getList(key, null); | |
} | |
public boolean remove(String key) { | |
key = validateKey(key); | |
try { | |
return cache.remove(key.toLowerCase()); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return false; | |
} | |
@SuppressWarnings("unchecked") | |
public <T> List<T> getAll(Class<T> clazz) { | |
return getAll(null, clazz); | |
} | |
public <T> List<T> getAll(String prefix, Class<T> clazz) { | |
List<T> list = new ArrayList<>(1); | |
File dir = cache.getDirectory(); | |
File[] files = dir.listFiles(); | |
if (files != null) { | |
list = new ArrayList<>(files.length); | |
for (File file : files) { | |
String fileName = file.getName(); | |
if ((!TextUtils.isEmpty(prefix) && fileName.startsWith(prefix) && fileName.indexOf(".") > 0) | |
|| (TextUtils.isEmpty(prefix) && fileName.indexOf(".") > 0)) { | |
String key = fileName.substring(0, fileName.indexOf(".")); | |
T value = get(key, clazz); | |
list.add(value); | |
} | |
} | |
} | |
return list; | |
} | |
public void clear() { | |
try { | |
cache.delete(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
public boolean exists(String key) { | |
key = validateKey(key); | |
DiskLruCache.Snapshot snapshot = null; | |
try { | |
snapshot = cache.get(key.toLowerCase()); | |
return snapshot != null && snapshot.getLength(0) > 0; | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
if (snapshot != null) { | |
snapshot.close(); | |
} | |
} | |
return false; | |
} | |
@Override | |
public void close() { | |
try { | |
cache.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
@SuppressWarnings("unused") | |
public void shouldSaveInUI() { | |
this.saveInUI = true; | |
} | |
private static class StoreParcelableValueTask implements Runnable { | |
private final DiskLruCache cache; | |
private final Parcel value; | |
private final String key; | |
public StoreParcelableValueTask(DiskLruCache cache, Parcel value, String key) { | |
this.value = value; | |
this.key = key; | |
this.cache = cache; | |
} | |
@Override | |
public void run() { | |
saveValue(cache, value, key); | |
} | |
} | |
private static void saveValue(DiskLruCache cache, Parcel value, String key) { | |
if (cache == null) return; | |
key = key.toLowerCase(); | |
try { | |
final String skey = key.intern(); | |
synchronized (skey) { | |
DiskLruCache.Editor editor = cache.edit(key); | |
OutputStream outputStream = editor.newOutputStream(0); | |
writeBytesToStream(outputStream, value.marshall()); | |
editor.commit(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
value.recycle(); | |
} | |
} | |
public static byte[] getBytesFromStream(InputStream is) throws IOException { | |
InputStream ins = is; | |
if (crypto.isAvailable()) { | |
try { | |
ins = crypto.getCipherInputStream(ins, new Entity("data.dat")); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | |
try { | |
byte[] data = new byte[1024]; | |
int count; | |
while ((count = ins.read(data, 0, data.length)) != -1) { | |
buffer.write(data, 0, count); | |
} | |
buffer.flush(); | |
return buffer.toByteArray(); | |
} finally { | |
ins.close(); | |
buffer.close(); | |
} | |
} | |
public static void writeBytesToStream(OutputStream outputStream, byte[] bytes) throws IOException { | |
OutputStream os = outputStream; | |
if (crypto.isAvailable()) { | |
try { | |
os = crypto.getCipherOutputStream(outputStream, new Entity("data.dat")); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
os.write(bytes); | |
os.flush(); | |
os.close(); | |
} | |
public static int getVersionCode(Context context) { | |
int result = 0; | |
try { | |
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); | |
result = pInfo.versionCode; | |
} catch (PackageManager.NameNotFoundException e) { | |
e.printStackTrace(); | |
} | |
return result; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
public interface DiskCache<T> { | |
/** | |
* Sets the value to {@code value}. | |
*/ | |
void set(String key, T value); | |
/** | |
* Returns a value by {@code key}, or null if it doesn't | |
* exist is not currently readable. If a value is returned, it is moved to | |
* the head of the LRU queue. | |
*/ | |
<T> T get(String key, Class<T> clazz); | |
/** | |
* Drops the entry for {@code key} if it exists and can be removed. Entries | |
* actively being edited cannot be removed. | |
* | |
* @return true if an entry was removed. | |
*/ | |
boolean remove(String key); | |
/** | |
* Returns all values from cache directory if all files are same type | |
* | |
* @return | |
*/ | |
<T> List<T> getAll(Class<T> clazz); | |
/** | |
* Deletes all file from cache directory | |
*/ | |
void clear(); | |
/** | |
* Returns true if there is file by {@code key} in cache folder | |
* | |
* @param key | |
* @return | |
*/ | |
boolean exists(String key); | |
/** | |
* Closes this cache. Stored values will remain on the filesystem. | |
*/ | |
void close(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import java.io.File; | |
import java.io.IOException; | |
public class ParcelDiskCache extends AbstractParcelDiskCache { | |
private static ParcelDiskCache sInstance; | |
private ParcelDiskCache(Context context, String name, long maxSize) throws IOException { | |
super(context, name, maxSize); | |
} | |
public static ParcelDiskCache getInstance(Context context) throws IOException { | |
if (sInstance == null) { | |
sInstance = new ParcelDiskCache(context, "objects", 5 * 1024 * 1024); //5MB store | |
} | |
return sInstance; | |
} | |
@Override | |
File getCacheDirectory(Context context) { | |
return context.getCacheDir(); | |
} | |
@Override | |
public void close() { | |
super.close(); | |
sInstance = null; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import java.io.File; | |
import java.io.IOException; | |
public class ParcelDiskStore extends AbstractParcelDiskCache { | |
private static ParcelDiskStore sInstance; | |
private ParcelDiskStore(Context context, String name, long maxSize) throws IOException { | |
super(context, name, maxSize); | |
} | |
public static ParcelDiskStore getInstance(Context context) throws IOException { | |
if (sInstance == null) { | |
sInstance = new ParcelDiskStore(context, "objects", 50 * 1024 * 1024); //50MB store | |
} | |
return sInstance; | |
} | |
@Override | |
File getCacheDirectory(Context context) { | |
return context.getFilesDir(); | |
} | |
@Override | |
public void close() { | |
super.close(); | |
sInstance = null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment