app/src/main/java/ca/marshallasch/veil/utilities/Util.java
package ca.marshallasch.veil.utilities;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.inputmethod.InputMethodManager;
import com.google.protobuf.Timestamp;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import ca.marshallasch.veil.R;
import ca.marshallasch.veil.proto.DhtProto;
/**
* This class is for application wide static utility functions to help reduce the amount of repeated
* code.
*
* @author Marshall Asch
* @version 1.0
* @since 2018-06-05
*/
public class Util
{
// this class can not be instantiated
private Util() {}
/**
* This utility function will convert the number of milliseconds since the epoch,
* (January 1, 1970, 00:00:00 GMT) to a Protobuf Timestamp object.
*
* @param millis the number of milliseconds
* @return the created timestamp object
*/
@NonNull
public static Timestamp millisToTimestamp(long millis) {
return com.google.protobuf.Timestamp.newBuilder()
.setSeconds(millis / 1000)
.setNanos((int) ((millis % 1000) * 1000000))
.build();
}
public static long timestampToMillis(@NonNull Timestamp time) {
long seconds = time.getSeconds();
long nanos = time.getNanos();
return seconds * 1000 + (nanos / 1000000);
}
/**
* Creates a {@link Date} item from the {@link Timestamp} object.
* @param timestamp the timestamp to convert
* @return a new Date object with the same time.
*/
@NonNull
public static Date timestampToDate (@NonNull Timestamp timestamp) {
long seconds = timestamp.getSeconds();
long nanos = timestamp.getNanos();
long millis = seconds * 1000 + (nanos / 1000000);
return new Date(millis);
}
/**
* Hides Android's soft keyboard.
*
* @param activity the main activity of the app
*/
public static void hideKeyboard(@NonNull Activity activity) {
InputMethodManager in = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
if(in != null){
in.hideSoftInputFromWindow(activity.findViewById(android.R.id.content).getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
/**
* Generates a SHA256 hash of the data. the hsh digest is returned as a string.
*
* Example:
* "e4896ed08d7620 3c2266092 2487c 296304956d863f462 c34c34de5a625a9"
* @param data the data to hash
* @return the hash on success, null on failure.
*/
@Nullable
public static String generateHash(byte[] data) {
//handle null data
if (data == null){
return null;
}
byte[] hash;
StringBuilder hashStr = new StringBuilder();
try {
MessageDigest dm = MessageDigest.getInstance("SHA-256");
hash = dm.digest(data);
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
// convert the hash byte array to a string
for (byte b : hash) {
hashStr.append(String.format("%2x", b));
}
return hashStr.toString();
}
/**
* This will create a new post object with the <code>UUID</code> field set to the hash of the
* object. To verify the the hash of the object, remove the uuid field then hash the post.
* This will set the anonymous field to false
*
* @param title string title of the post
* @param message the message body of the post
* @param author the {@link ca.marshallasch.veil.proto.DhtProto.User} object
* @param tags a list of tag strings
* @return a post object
*/
public static DhtProto.Post createPost(String title, String message, @NonNull DhtProto.User author, @Nullable List<String> tags) {
return createPost(title, message, author, tags, false);
}
/**
* This will create a new post object with the <code>UUID</code> field set to the hash of the
* object. To verify the the hash of the object, remove the uuid field then hash the post.
*
* @param title string title of the post
* @param message the message body of the post
* @param author the {@link ca.marshallasch.veil.proto.DhtProto.User} object
* @param tags a list of tag strings
* @param anonymous whether or not the authors name will be displayed when it is shown.
* @return a post object
*/
public static DhtProto.Post createPost(String title, String message, @NonNull DhtProto.User author, @Nullable List<String> tags, boolean anonymous) {
DhtProto.Post.Builder postBuilder = DhtProto.Post.newBuilder();
// set attributes
postBuilder.setTitle(title);
postBuilder.setMessage(message);
postBuilder.setAuthorName(author.getFirstName() + " " + author.getLastName());
postBuilder.setAuthorId(author.getUuid());
postBuilder.setTimestamp(millisToTimestamp(System.currentTimeMillis()));
postBuilder.setAnonymous(anonymous);
// add the tags if there are any
if (tags != null) {
postBuilder.addAllTags(tags);
}
DhtProto.Post post = postBuilder.build();
String hash = generateHash(post.toByteArray());
post = DhtProto.Post.newBuilder(post)
.setUuid(hash)
.build();
return post;
}
/**
* Ths function will save the user so they don't need to login every time.
* @param activity the activity for the shared preferences to use
* @param username the username of the user
* @param password the password of the user.
*/
public static void rememberUserName(@NonNull Activity activity, @NonNull String username, @NonNull String password) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(activity.getString(R.string.pref_username), username);
editor.putString(activity.getString(R.string.pref_passwords), password);
editor.apply();
}
/**
* This will clear the known user so that if the user logs out they wont be remembered.
* @param activity the activity for the shared preferences to use
*/
public static void clearKnownUser(Activity activity){
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = sharedPref.edit();
editor.remove(activity.getString(R.string.pref_username));
editor.remove(activity.getString(R.string.pref_passwords));
editor.apply();
}
/**
* This will get the username of the remembered user.
*
* @param activity the activity for the shared preferences to use
* @return the username if there is a known one otherwise null
*/
@Nullable
public static String getKnownUsername(Activity activity) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity);
return sharedPref.getString(activity.getString(R.string.pref_username), null);
}
/**
* This will get the password of the remembered user.
*
* @param activity the activity for the shared preferences to use
* @return the password if there is a known one otherwise null
*/
@Nullable
public static String getKnownPassword(Activity activity)
{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity);
return sharedPref.getString(activity.getString(R.string.pref_passwords), null);
}
/**
* Create a new comment object, that is not assigned to a specific post.
* Note that this does not set the postID field, and the hash is made with the field unset.
* This will set anonymous to false.
*
* @param message the body of the comment
* @param author the author who wrote the comment
* @param postHash the postID that it belongs to
* @return a comment object with the postID field set
*/
public static DhtProto.Comment createComment(@NonNull String message, @NonNull DhtProto.User author, @Nullable String postHash) {
return createComment(message, author, postHash, false);
}
/**
* Create a new comment object, that is not assigned to a specific post.
* Note that this does not set the postID field, and the hash is made with the field unset.
*
* @param message the body of the comment
* @param author the author who wrote the comment
* @param postHash the postID that it belongs to
* @param anonymous if the post is anonymous or not
* @return a comment object with the postID field set
*/
public static DhtProto.Comment createComment(@NonNull String message, @NonNull DhtProto.User author, @Nullable String postHash, boolean anonymous) {
// set attributed
DhtProto.Comment.Builder builder = DhtProto.Comment.newBuilder();
builder.setMessage(message);
builder.setAuthorName(author.getFirstName() + " " + author.getLastName());
builder.setAuthorId(author.getUuid());
builder.setTimestamp(millisToTimestamp(System.currentTimeMillis()));
builder.setAnonymous(anonymous);
if (postHash != null) {
builder.setPostId(postHash);
}
DhtProto.Comment comment = builder.build();
String hash = generateHash(comment.toByteArray());
// set the hash
comment = DhtProto.Comment.newBuilder(comment)
.setUuid(hash)
.build();
return comment;
}
/**
* This checks that the passwords that have been entered match and meet the
* minimum complexity requirements.
* @param password the password that the user entered
* @param passwordConf the confirmation of the password
* @return {@link PasswordState}
*/
public static PasswordState checkPasswords(@NonNull String password, @NonNull String passwordConf)
{
if (password.length() == 0 || passwordConf.length() == 0) {
return PasswordState.MISSING;
}
int numUpper = 0;
int numLower = 0;
int numDigit = 0;
int numSpecial = 0;
int length = password.length();
// make sure that they match before checking complexity
if (!password.equals(passwordConf)) {
return PasswordState.MISMATCH;
}
// check complexity
for (int i = 0; i < length; i++) {
if (Character.isUpperCase(password.charAt(i))) {
numUpper++;
} else if (Character.isLowerCase(password.charAt(i))) {
numLower++;
} else if (Character.isDigit(password.charAt(i))) {
numDigit++;
} else if (Character.getType(password.charAt(i)) == Character.OTHER_PUNCTUATION) {
numSpecial++;
}
}
if (length < 8 || numUpper == 0 || numLower == 0 || numDigit == 0 || numSpecial == 0) {
return PasswordState.TOO_SIMPLE;
}
return PasswordState.GOOD;
}
/**
* Simple check of the email format, Note that the email does not need to be RFC 5322
* compliment, it just needs to be something@something.something.else.
* @return true if the email is valid otherwise false.
*/
public static boolean checkEmail(@Nullable String email) {
return email != null &&
email.length() != 0 &&
email.matches("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$");
}
/**
* Generates a random hash
* @return a random hash based on a hardcoded string and the system time
*/
public static int getRandomRequestCode(){
return (("randomString" + System.currentTimeMillis()).hashCode());
}
}