Created
February 23, 2016 13:30
-
-
Save loiclefloch/20be5ad39a74c912e030 to your computer and use it in GitHub Desktop.
Android tools
This file contains 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; | |
import retrofit.RequestInterceptor; | |
import retrofit.RestAdapter; | |
import retrofit.http.Body; | |
import retrofit.http.GET; | |
import retrofit.http.POST; | |
import retrofit.http.PUT; | |
import retrofit.http.Path; | |
public class ApiManager { | |
private static ApiManager instance; | |
public BookmarkService bookmarkService; | |
public TagService tagService; | |
RestAdapter catalogRestAdapter; | |
public static ApiManager getInstance() { | |
if (instance == null) { | |
instance = new ApiManager(); | |
} | |
return instance; | |
} | |
public ApiManager() { | |
RequestInterceptor requestInterceptor = new RequestInterceptor() { | |
@Override | |
public void intercept(RequestFacade request) { | |
request.addHeader(Constants.api.HEADER_API_KEY, Constants.api.API_TEST_KEY); | |
} | |
}; | |
catalogRestAdapter = new RestAdapter.Builder() | |
.setEndpoint(Constants.api.URL) | |
.setRequestInterceptor(requestInterceptor) | |
.setLogLevel((BuildConfig.DEBUG ? | |
RestAdapter.LogLevel.FULL : RestAdapter.LogLevel.NONE)) | |
.build(); | |
bookmarkService = catalogRestAdapter.create(BookmarkService.class); | |
tagService = catalogRestAdapter.create(TagService.class); | |
} | |
public void getBookmarks(RestCallback<List<Bookmark>> callback) { | |
bookmarkService.get(callback); | |
} | |
public void postBookmark(Bookmark bookmark, RestCallback<Bookmark> callback) { | |
bookmarkService.post(bookmark, callback); | |
} | |
public void putBookmark(Bookmark bookmark, RestCallback<Bookmark> callback) { | |
bookmarkService.put(bookmark.getId(), bookmark, callback); | |
} | |
public void getTags(RestCallback<List<Tag>> callback) { | |
tagService.get(callback); | |
} | |
// ----------- Services. | |
public interface BookmarkService { | |
// -- The api errors code | |
int BOOKMARK_URL_EMPTY = 4001; | |
int BOOKMARK_ALREADY_EXISTS = 4002; | |
int INVALID_URL = 4002; | |
int WEBSITE_NO_TITLE = 4003; | |
int MONGODB_ERROR = 5001; // Failed to save the bookmark | |
int BOOKMARK_NOT_FOUND = 1002; | |
// -- The routes | |
@GET("/bookmark") | |
void get(RestCallback<List<Bookmark>> callback); | |
@POST("/bookmark") | |
void post(@Body Bookmark bookmark, RestCallback<Bookmark> callback); | |
@PUT("/bookmark/{id}") | |
void put(@Path("id") String id, @Body Bookmark bookmark, RestCallback<Bookmark> callback); | |
} | |
public interface TagService { | |
// -- The api errors code | |
// TODO | |
// -- The routes | |
@GET("/tag") | |
void get(RestCallback<List<Tag>> callback); | |
@POST("/tag") | |
void post(@Body Tag bookmark, RestCallback<Tag> callback); | |
@PUT("/tag") | |
void put(@Body Tag bookmark, RestCallback<Tag> callback); | |
} | |
} |
This file contains 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.app.ProgressDialog; | |
import android.content.Context; | |
import android.os.Bundle; | |
import android.support.v7.app.AppCompatActivity; | |
import android.view.MenuItem; | |
import android.view.View; | |
import android.view.inputmethod.InputMethodManager; | |
import butterknife.ButterKnife; | |
public class BaseAppCompatActivity extends AppCompatActivity | |
implements ViewInterface { | |
ProgressDialog loadingDialog; | |
View parentLayout; | |
protected android.support.v7.app.ActionBar toolbar; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
initProgressDialog(); | |
} | |
private void initProgressDialog() { | |
loadingDialog = new ProgressDialog(this); | |
loadingDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); | |
loadingDialog.setMessage(getText(R.string.loading)); | |
loadingDialog.setCancelable(false); | |
} | |
/** | |
* Setup the toolbar | |
* @param title the title of the view. | |
*/ | |
protected void initToolbar(String title) { | |
toolbar = getSupportActionBar(); | |
if (toolbar != null) { | |
setToolbarTitle(title); | |
} | |
if (getSupportActionBar() != null) { | |
getSupportActionBar().setDisplayHomeAsUpEnabled(true); | |
} | |
} | |
/** | |
* Remove the back icon on the toolbar. <br> | |
* To disable back button, override onBackPressed function to do nothing. | |
*/ | |
protected void removeBackIconOnToolbar() { | |
if (getSupportActionBar() != null) { | |
getSupportActionBar().setDisplayHomeAsUpEnabled(false); | |
} | |
} | |
@Override | |
public boolean onOptionsItemSelected(MenuItem item) { | |
switch (item.getItemId()) { | |
// Respond to the action bar's Up/Home button | |
case android.R.id.home: | |
onBackPressed(); | |
return true; | |
} | |
return super.onOptionsItemSelected(item); | |
} | |
protected void initView(int id, int parentLayoutId) { | |
setContentView(id); | |
this.parentLayout = findViewById(parentLayoutId); | |
ButterKnife.bind(this); | |
} | |
// --- View interface | |
@Override | |
public void displayError(String message) { | |
if (parentLayout != null) { | |
ViewTools.displayError(parentLayout, message); | |
} | |
} | |
@Override | |
public void displayToast(String title) { | |
ViewTools.displayToast(this, title); | |
} | |
@Override | |
public void showLoading() { | |
loadingDialog.show(); | |
} | |
@Override | |
public void hideLoading() { | |
loadingDialog.hide(); | |
} | |
@Override | |
public void hideKeyboard() { | |
InputMethodManager imm = (InputMethodManager) getSystemService( | |
Context.INPUT_METHOD_SERVICE); | |
View currentFocus = getCurrentFocus(); | |
if (currentFocus != null) { | |
imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); | |
} | |
} | |
@Override | |
public void setToolbarTitle(String title) { | |
toolbar.setTitle(title); | |
} | |
} |
This file contains 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.text.TextUtils; | |
import com.google.gson.Gson; | |
import com.google.gson.GsonBuilder; | |
import com.orhanobut.logger.Logger; | |
import java.io.Serializable; | |
public abstract class Model implements Serializable { | |
protected abstract String getClassName(); | |
@Override | |
public String toString() { | |
Gson gson = new GsonBuilder().create(); | |
String json = gson.toJson(this); | |
if (TextUtils.isEmpty(getClassName())) { | |
return json; | |
} | |
return getClassName() + ": " + json; | |
} | |
private void dump() { | |
Logger.i(String.valueOf(this)); | |
} | |
public <T extends Model> T copy(Class<T> type) { | |
Gson gson = new GsonBuilder().create(); | |
return gson.fromJson(gson.toJson(this), type); | |
} | |
} |
This file contains 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.DialogInterface; | |
import android.support.v7.app.AlertDialog; | |
import android.widget.Toast; | |
/** | |
* Tools to print notifications to the user | |
* - Toast and snackbar will never appear if the notifications are disabled for the app | |
* - AlertDialog appears | |
* So we check if the notifications are disabled. If it is, we use an AlertDialog with juste a “ok” button. | |
*/ | |
public final class Notification { | |
/** | |
* Display a toast if possible, an alert dialog otherwise | |
* @param context The current context | |
* @param message The message to display | |
*/ | |
public static void show(Context context, String message) { | |
if (PermissionUtil.isNotificationEnabled(context)) { | |
Toast.makeText(context, message, Toast.LENGTH_LONG).show(); | |
} | |
else { | |
showSimpleAlertDialog(context, message); | |
} | |
} | |
/** | |
* Display a toast with custom gravity and position if possible, an alert dialog otherwise | |
* @param context The current context | |
* @param message The message to display | |
*/ | |
public static void showWithGravity(Context context, String message, int gravity, int xOffset, int yOffset) { | |
if (PermissionUtil.isNotificationEnabled(context)) { | |
Toast toast = Toast.makeText(context, message, Toast.LENGTH_SHORT); | |
toast.setGravity(gravity, xOffset, yOffset); | |
toast.show(); | |
Toast.makeText(context, message, Toast.LENGTH_LONG).show(); | |
} | |
else { | |
showSimpleAlertDialog(context, message); | |
} | |
} | |
/** | |
* Display an alert dialog with only an "ok" button | |
* @param context The current context | |
* @param message The message to display | |
*/ | |
private static void showSimpleAlertDialog(Context context, String message) { | |
try { | |
new AlertDialog.Builder(context) | |
.setTitle("") | |
.setMessage(message) | |
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
} | |
}) | |
.show(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
// This exception occurred if the context is not good (do not use getApplicationContext() but Class.this) | |
// Also, the activity must have android:theme="@style/AppTheme" defined on the AndroidManifest | |
if (Constants.DEV_MODE) { | |
throw new AssertionError("Invalid context to display the alert dialog"); | |
} | |
} | |
} | |
} |
This file contains 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
public interface PermissionCallback { | |
void retry(); | |
void refused(); | |
} |
This file contains 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.app.Activity; | |
import android.app.AlertDialog; | |
import android.app.AppOpsManager; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.Intent; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.PackageManager; | |
import android.net.Uri; | |
import android.provider.Settings; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
/** | |
* Utility class that wraps access to the runtime permissions API in M and provides basic helper | |
* methods. | |
*/ | |
public abstract class PermissionUtil { | |
/** | |
* Id to identify a permission setting request | |
*/ | |
public static final int REQUEST_PERMISSION_SETTING = 90; | |
/** | |
* Check that all given permissions have been granted by verifying that each entry in the | |
* given array is of the value {@link PackageManager#PERMISSION_GRANTED}. | |
* | |
* @see Activity#onRequestPermissionsResult(int, String[], int[]) | |
*/ | |
public static boolean verifyPermissions(int[] grantResults) { | |
// At least one result must be checked. | |
if (grantResults.length < 1) { | |
return false; | |
} | |
// Verify that each required permission has been granted, otherwise return false. | |
for (int result : grantResults) { | |
if (result != PackageManager.PERMISSION_GRANTED) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Call this to tell the user why we need the permission, and redirect him to the settings if | |
* click on "Ok" | |
* | |
* @param activity the current activity | |
* @param text the text to explain why we need this permission (begin with without_permission_) | |
*/ | |
public static void displayPermissionTotallyRefusedAlert(final Activity activity, String text) { | |
AlertDialog alertDialog = new AlertDialog.Builder(activity).create(); | |
alertDialog.setTitle(R.string.permission_required); | |
alertDialog.setMessage(text); | |
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", | |
new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
openSettings(activity); | |
} | |
}); | |
alertDialog.show(); | |
} | |
/** | |
* Call this to give an explanation about the permission asked after the user refuse it.<br> | |
* It will ask the user if he wants to have the possibility to give the permission ("Retry") | |
* or not ("I am sure") | |
* | |
* @param activity the current activity | |
* @param text the text to display (begin with need_permission_). We add "ask_if_sure_to_deny_permission" question text at the end. | |
* @param callback the PermissionCallback.<br> | |
* retry() is called when the user wants to have the possibility to give the permission.<br> | |
* refused() is called when the user does not want to give the permission at all. | |
*/ | |
public static void displayPermissionInfoMessage(Activity activity, String text, final PermissionCallback callback) { | |
AlertDialog alertDialog = new AlertDialog.Builder(activity).create(); | |
alertDialog.setTitle(R.string.error); | |
alertDialog.setMessage(text + " " + activity.getString(R.string.ask_if_sure_to_deny_permission)); | |
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.retry), | |
new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
callback.retry(); | |
} | |
}); | |
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getString(R.string.i_am_sure), | |
new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
callback.refused(); | |
} | |
}); | |
alertDialog.show(); | |
} | |
/** | |
* Create and start an intent to redirect the user to the app settings. The user need to go to the "Permissions" section | |
* and enable the asked permissions. | |
* | |
* @param activity | |
*/ | |
public static void openSettings(Activity activity) { | |
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); | |
Uri uri = Uri.fromParts("package", activity.getPackageName(), null); | |
intent.setData(uri); | |
activity.startActivityForResult(intent, REQUEST_PERMISSION_SETTING); | |
} | |
public static void displayLocationSettingsAlert(final Activity activity) { | |
AlertDialog.Builder alertDialog = new AlertDialog.Builder(activity); | |
alertDialog.setTitle(activity.getBaseContext().getText(R.string.near_resto_popup_gps_title)); | |
alertDialog.setMessage(activity.getBaseContext().getText(R.string.near_resto_popup_gps_content)); | |
alertDialog.setPositiveButton(activity.getBaseContext().getText(R.string.settings), new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); | |
activity.startActivity(intent); | |
dialog.cancel(); | |
} | |
}); | |
alertDialog.setNegativeButton(activity.getBaseContext().getText(R.string.cancel), new DialogInterface.OnClickListener() { | |
public void onClick(DialogInterface dialog, int which) { | |
dialog.cancel(); | |
} | |
}); | |
alertDialog.show(); | |
} | |
private static final String CHECK_OP_NO_THROW = "checkOpNoThrow"; | |
private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"; | |
/** | |
* !!! Use this function only for notificaitons like toast and snackbar. Because below | |
* KITKAT, it will always return false to force use a custom view instead the toast. | |
* @param context the current context. | |
* @return true if the notifications are enabled and we can display a toast. | |
*/ | |
public static boolean isNotificationEnabled(Context context) { | |
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { | |
//Source: http://stackoverflow.com/questions/11649151/android-4-1-how-to-check-notifications-are-disabled-for-the-application/30108004#30108004 | |
AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); | |
ApplicationInfo appInfo = context.getApplicationInfo(); | |
String pkg = context.getApplicationContext().getPackageName(); | |
int uid = appInfo.uid; | |
Class appOpsClass = null; /* Context.APP_OPS_MANAGER */ | |
try { | |
appOpsClass = Class.forName(AppOpsManager.class.getName()); | |
Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, String.class); | |
Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION); | |
int value = (int) opPostNotificationValue.get(Integer.class); | |
return ((int) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return false; | |
} | |
// We don't have any way to found if notification are enable before KITKAT. So we consider it's not. | |
return false; | |
} | |
} |
This file contains 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 com.google.gson.Gson; | |
import retrofit.Callback; | |
import retrofit.RetrofitError; | |
import retrofit.client.Response; | |
import retrofit.mime.TypedByteArray; | |
/** | |
* Override callback to get a simple error | |
* Override callback to get a simple error | |
* @param <T> | |
*/ | |
public abstract class RestCallback<T> implements Callback<T> | |
{ | |
// -- We need to create those functions each time we create a RestCallback | |
public abstract void success(T t); | |
public abstract void failure(RestError restError); | |
@Override | |
public void success(T t, Response response) { | |
success(t); | |
} | |
@Override | |
public void failure(RetrofitError error) | |
{ | |
// Try to unserialize the body | |
RestError restError = null; | |
try { | |
if (error.getResponse() != null) { | |
restError = (RestError) error.getBodyAs(RestError.class); | |
if (restError == null || restError.getCode() == null) { | |
TypedByteArray jsonBody = (TypedByteArray)error.getResponse().getBody(); | |
if (jsonBody != null) { | |
restError = new Gson().fromJson(new String(jsonBody.getBytes()), RestError.class); | |
} | |
} | |
} | |
} | |
catch (RuntimeException e) { | |
e.printStackTrace(); | |
} | |
// it's not a valid Api error with a correct json. | |
if (restError == null) { | |
restError = new RestError(); | |
} | |
// We save the RetrofitError | |
restError.setError(error); | |
failure(restError); | |
} | |
} |
This file contains 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 com.google.gson.annotations.Expose; | |
import com.google.gson.annotations.SerializedName; | |
import java.util.Objects; | |
import retrofit.RetrofitError; | |
/** | |
* RestError represent an error from the API. | |
*/ | |
public class RestError | |
{ | |
private static final Integer DEFAULT_ERROR_CODE = -1; | |
/** | |
* The api error code or the http status if the json can't be unserialize. | |
*/ | |
@SerializedName("code") | |
private Integer code = DEFAULT_ERROR_CODE; | |
/** | |
* The api error message | |
*/ | |
@SerializedName("message") | |
private String message = ""; | |
/** | |
* The retrofit error | |
*/ | |
@Expose | |
private RetrofitError error; | |
/** | |
* Return if the code contains an api error code or and http error code | |
* @return boolean | |
*/ | |
public boolean isApiError() { | |
return this.code != null && !Objects.equals(this.code, DEFAULT_ERROR_CODE); | |
} | |
public retrofit.client.Response getResponse() { | |
return error.getResponse(); | |
} | |
public int getStatusCode() { | |
return error.getResponse().getStatus(); | |
} | |
@Override | |
public String toString() { | |
return "ResError\n" | |
+ "[" + code + "] " + message + "\n" | |
+ error; | |
} | |
// -- getters and setters | |
public String getMessage() { | |
return message; | |
} | |
public void setMessage(String strMessage) { | |
this.message = strMessage; | |
} | |
public Integer getCode() { | |
return code; | |
} | |
public void setCode(Integer code) { | |
this.code = code; | |
} | |
public RetrofitError getError() { | |
return error; | |
} | |
public void setError(RetrofitError error) { | |
this.error = error; | |
} | |
} |
This file contains 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.io.UnsupportedEncodingException; | |
import java.security.GeneralSecurityException; | |
import java.security.InvalidAlgorithmParameterException; | |
import java.security.InvalidKeyException; | |
import java.security.MessageDigest; | |
import java.security.NoSuchAlgorithmException; | |
import javax.crypto.Cipher; | |
import javax.crypto.spec.IvParameterSpec; | |
import javax.crypto.spec.SecretKeySpec; | |
import android.content.Context; | |
import android.content.SharedPreferences; | |
import android.util.Base64; | |
public class SecurePreferences { | |
public static class SecurePreferencesException extends RuntimeException { | |
public SecurePreferencesException(Throwable e) { | |
super(e); | |
} | |
} | |
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; | |
private static final String KEY_TRANSFORMATION = "AES/ECB/PKCS5Padding"; | |
private static final String SECRET_KEY_HASH_TRANSFORMATION = "SHA-256"; | |
private static final String CHARSET = "UTF-8"; | |
private final boolean encryptKeys; | |
private final Cipher writer; | |
private final Cipher reader; | |
private final Cipher keyWriter; | |
private final SharedPreferences preferences; | |
/** | |
* This will initialize an instance of the SecurePreferences class | |
* | |
* @param context | |
* your current context. | |
* @param preferenceName | |
* name of preferences file (preferenceName.xml) | |
* @param secureKey | |
* the key used for encryption, finding a good key scheme is hard. Hardcoding your key in the | |
* application is bad, but better than plaintext preferences. Having the user enter the key | |
* upon application launch is a safe(r) alternative, but annoying to the user. | |
* @param encryptKeys | |
* settings this to false will only encrypt the values, true will encrypt both values and gcm. | |
* Keys can contain a lot of information about the plaintext value of the value which can be | |
* used to decipher the value. | |
* @throws SecurePreferencesException | |
*/ | |
public SecurePreferences(Context context, String preferenceName, String secureKey, boolean encryptKeys) | |
throws SecurePreferencesException { | |
try { | |
this.writer = Cipher.getInstance(TRANSFORMATION); | |
this.reader = Cipher.getInstance(TRANSFORMATION); | |
this.keyWriter = Cipher.getInstance(KEY_TRANSFORMATION); | |
initCiphers(secureKey); | |
this.preferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE); | |
this.encryptKeys = encryptKeys; | |
} | |
catch (GeneralSecurityException | UnsupportedEncodingException e) { | |
throw new SecurePreferencesException(e); | |
} | |
} | |
private void initCiphers(String secureKey) throws UnsupportedEncodingException, | |
NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException { | |
IvParameterSpec ivSpec = getIv(); | |
SecretKeySpec secretKey = getSecretKey(secureKey); | |
writer.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); | |
reader.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); | |
keyWriter.init(Cipher.ENCRYPT_MODE, secretKey); | |
} | |
private IvParameterSpec getIv() { | |
byte[] iv = new byte[writer.getBlockSize()]; | |
System.arraycopy("fldsjfodasjifudslfjdsaofshaufihadsf".getBytes(), 0, iv, 0, writer.getBlockSize()); | |
return new IvParameterSpec(iv); | |
} | |
private SecretKeySpec getSecretKey(String key) throws UnsupportedEncodingException, | |
NoSuchAlgorithmException { | |
byte[] keyBytes = createKeyBytes(key); | |
return new SecretKeySpec(keyBytes, TRANSFORMATION); | |
} | |
private byte[] createKeyBytes(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException { | |
MessageDigest md = MessageDigest.getInstance(SECRET_KEY_HASH_TRANSFORMATION); | |
md.reset(); | |
return md.digest(key.getBytes(CHARSET)); | |
} | |
public void put(String key, String value) { | |
if (value == null) { | |
preferences.edit().remove(toKey(key)).apply(); | |
} else { | |
putValue(toKey(key), value); | |
} | |
} | |
public boolean containsKey(String key) { | |
return preferences.contains(toKey(key)); | |
} | |
public void removeValue(String key) { | |
preferences.edit().remove(toKey(key)).apply(); | |
} | |
public String getString(String key) throws SecurePreferencesException { | |
if (preferences.contains(toKey(key))) { | |
String securedEncodedValue = preferences.getString(toKey(key), ""); | |
return decrypt(securedEncodedValue); | |
} | |
return null; | |
} | |
public void clear() { | |
preferences.edit().clear().apply(); | |
} | |
private String toKey(String key) { | |
if (encryptKeys) | |
return encrypt(key, keyWriter); | |
else | |
return key; | |
} | |
private void putValue(String key, String value) throws SecurePreferencesException { | |
String secureValueEncoded = encrypt(value, writer); | |
preferences.edit().putString(key, secureValueEncoded).apply(); | |
} | |
private String encrypt(String value, Cipher writer) throws SecurePreferencesException { | |
byte[] secureValue; | |
try { | |
secureValue = convert(writer, value.getBytes(CHARSET)); | |
} catch (UnsupportedEncodingException e) { | |
throw new SecurePreferencesException(e); | |
} | |
return Base64.encodeToString(secureValue, Base64.NO_WRAP); | |
} | |
private String decrypt(String securedEncodedValue) { | |
byte[] securedValue = Base64.decode(securedEncodedValue, Base64.NO_WRAP); | |
byte[] value = convert(reader, securedValue); | |
try { | |
return new String(value, CHARSET); | |
} catch (UnsupportedEncodingException e) { | |
throw new SecurePreferencesException(e); | |
} | |
} | |
private static byte[] convert(Cipher cipher, byte[] bs) throws SecurePreferencesException { | |
try { | |
return cipher.doFinal(bs); | |
} catch (Exception e) { | |
throw new SecurePreferencesException(e); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment