package com.seafile.seadroid2.util;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.Uri;
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.seafile.seadroid2.R;
import com.seafile.seadroid2.SeadroidApplication;
import com.seafile.seadroid2.data.SeafRepo;
import com.seafile.seadroid2.fileschooser.SelectableFile;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
public class Utils {
public static final String MIME_APPLICATION_OCTET_STREAM = "application/octet-stream";
public static final String AUTHORITY = "com.seafile.seadroid2";
public static final String PATH_SEPERATOR = "/";
// public static final String NOGROUP = "$nogroup";
public static final String PERSONAL_REPO = "personal_repo";
public static final String SHARED_REPO = "shared_repo";
private static final String DEBUG_TAG = "Utils";
private static final String HIDDEN_PREFIX = ".";
private static HashMap<String, Integer> suffixIconMap = null;
private Utils() {}
public static JSONObject parseJsonObject(String json) {
if (json == null) {
// the caller should not give null
Log.w(DEBUG_TAG, "null in parseJsonObject");
return null;
}
try {
return (JSONObject) new JSONTokener(json).nextValue();
} catch (Exception e) {
return null;
}
}
public static JSONArray parseJsonArrayByKey(@NonNull String json, @NonNull String key) throws JSONException {
String value = new JSONObject(json).optString(key);
if (!TextUtils.isEmpty(value))
return parseJsonArray(value);
else
return null;
}
public static JSONArray parseJsonArray(@NonNull String json) {
try {
return (JSONArray) new JSONTokener(json).nextValue();
} catch (Exception e) {
Log.e(DEBUG_TAG, "Could not parse json file", e);
return null;
}
}
public static String readFile(File file) {
Reader reader = null;
try {
try {
// TODO: detect a file's encoding
reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
} catch (UnsupportedEncodingException e) {
return null;
}
char[] buffer = new char[1024];
StringBuilder responseStrBuilder = new StringBuilder();
while (true) {
int len = reader.read(buffer, 0, 1024);
if (len == -1)
break;
responseStrBuilder.append(buffer, 0, len);
}
return responseStrBuilder.toString();
} catch (IOException e) {
return null;
} finally {
try {
if (reader != null)
reader.close();
} catch (Exception e) {
}
}
}
public static String getParentPath(String path) {
if (path == null) {
// the caller should not give null
Log.w(DEBUG_TAG, "null in getParentPath");
return null;
}
if (!path.contains("/")) {
return "/";
}
String parent = path.substring(0, path.lastIndexOf("/"));
if (parent.equals("")) {
return "/";
} else
return parent;
}
public static String fileNameFromPath(String path) {
if (path == null) {
// the caller should not give null
Log.w(DEBUG_TAG, "null in getParentPath");
return null;
}
return path.substring(path.lastIndexOf("/") + 1);
}
public static String readableFileSize(long size) {
if(size <= 0) return "0 KB";
final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
int digitGroups = (int) (Math.log10(size)/Math.log10(1000));
return new DecimalFormat("#,##0.#").format(size/Math.pow(1000, digitGroups)) + " " + units[digitGroups];
}
public static void writeFile(File file, String content) throws IOException {
OutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(content.getBytes("UTF-8"));
} finally {
try {
if (os != null)
os.close();
} catch (Exception e) {
// ignore
}
}
}
public static TreeMap<String, List<SeafRepo>> groupRepos(List<SeafRepo> repos) {
TreeMap<String, List<SeafRepo>> map = new TreeMap<String, List<SeafRepo>>();
String groupName = null;
for (SeafRepo repo : repos) {
List<SeafRepo> l;
if (repo.isGroupRepo)
groupName = repo.owner;
else if (repo.isPersonalRepo)
groupName = PERSONAL_REPO;
else if (repo.isSharedRepo)
groupName = SHARED_REPO;
l = map.get(groupName);
if (l == null) {
l = Lists.newArrayList();
map.put(groupName, l);
}
l.add(repo);
}
return map;
}
public static int getResIdforMimetype(String mimetype) {
if (mimetype == null)
return R.drawable.file;
if (mimetype.contains("pdf")) {
return R.drawable.file_pdf;
} else if (mimetype.contains("image/")) {
return R.drawable.file_image;
} else if (mimetype.contains("text")) {
return R.drawable.file_text;
} else if (mimetype.contains("audio")) {
return R.drawable.file_audio;
} else if (mimetype.contains("video")) {
return R.drawable.file_video;
} if (mimetype.contains("pdf")) {
return R.drawable.file_pdf;
} else if (mimetype.contains("msword") || mimetype.contains("ms-word")) {
return R.drawable.file_ms_word;
} else if (mimetype.contains("mspowerpoint") || mimetype.contains("ms-powerpoint")) {
return R.drawable.file_ms_ppt;
} else if (mimetype.contains("msexcel") || mimetype.contains("ms-excel")) {
return R.drawable.file_ms_excel;
} else if (mimetype.contains("openxmlformats-officedocument")) {
// see http://stackoverflow.com/questions/4212861/what-is-a-correct-mime-type-for-docx-pptx-etc
if (mimetype.contains("wordprocessingml")) {
return R.drawable.file_ms_word;
} else if (mimetype.contains("spreadsheetml")) {
return R.drawable.file_ms_excel;
} else if (mimetype.contains("presentationml")) {
return R.drawable.file_ms_ppt;
}
// } else if (mimetype.contains("application")) {
// return R.drawable.file_binary;
}
return R.drawable.file;
}
private static synchronized HashMap<String, Integer> getSuffixIconMap() {
if (suffixIconMap != null)
return suffixIconMap;
suffixIconMap = Maps.newHashMap();
suffixIconMap.put("pdf", R.drawable.file_pdf);
suffixIconMap.put("doc", R.drawable.file_ms_word);
suffixIconMap.put("docx", R.drawable.file_ms_word);
suffixIconMap.put("md", R.drawable.file_text);
suffixIconMap.put("markdown", R.drawable.file_text);
return suffixIconMap;
}
public static int getFileIcon(String name) {
String suffix = name.substring(name.lastIndexOf('.') + 1).toLowerCase();
if (suffix.length() == 0) {
return R.drawable.file;
}
HashMap<String, Integer> map = getSuffixIconMap();
Integer i = map.get(suffix);
if (i != null)
return i;
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix);
return getResIdforMimetype(mime);
}
public static boolean isViewableImage(String name) {
String suffix = name.substring(name.lastIndexOf('.') + 1).toLowerCase();
if (suffix.length() == 0)
return false;
if (suffix.equals("svg"))
// don't support svg preview
return false;
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix);
if (mime == null)
return false;
return mime.contains("image/");
}
public static boolean isNetworkOn() {
ConnectivityManager connMgr = (ConnectivityManager)
SeadroidApplication.getAppContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkInfo wifi = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if(wifi != null && wifi.isAvailable()
&& wifi.getDetailedState() == DetailedState.CONNECTED) {
return true;
}
NetworkInfo mobile = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if(mobile != null && mobile.isAvailable()
&& mobile.getDetailedState() == DetailedState.CONNECTED) {
return true;
}
return false;
}
public static boolean isWiFiOn() {
ConnectivityManager connMgr = (ConnectivityManager)
SeadroidApplication.getAppContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkInfo wifi = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if(wifi != null && wifi.isAvailable()
&& wifi.getDetailedState() == DetailedState.CONNECTED) {
return true;
}
return false;
}
public static String pathJoin (String first, String... rest) {
StringBuilder result = new StringBuilder(first);
for (String b: rest) {
boolean resultEndsWithSlash = result.toString().endsWith("/");
boolean bStartWithSlash = b.startsWith("/");
if (resultEndsWithSlash && bStartWithSlash) {
result.append(b.substring(1));
} else if (resultEndsWithSlash || bStartWithSlash) {
result.append(b);
} else {
result.append("/");
result.append(b);
}
}
return result.toString();
}
public static String removeLastPathSeperator(String path) {
if (TextUtils.isEmpty(path)) return null;
int size = path.length();
if (path.endsWith("/")) {
return path.substring(0, size - 1);
} else
return path;
}
/**
* Strip leading and trailing slashes
*/
public static String stripSlashes(String a) {
return a.replaceAll("^[/]*|[/]*$", "");
}
public static String getCurrentHourMinute() {
return (String) DateFormat.format("hh:mm", new Date());
}
/**
* Translate commit time to human readable time description
*/
public static String translateCommitTime(long timestampInMillis) {
long now = Calendar.getInstance().getTimeInMillis();
if (now <= timestampInMillis) {
return SeadroidApplication.getAppContext().getString(R.string.just_now);
}
long delta = (now - timestampInMillis) / 1000;
long secondsPerDay = 24 * 60 * 60;
long days = delta / secondsPerDay;
long seconds = delta % secondsPerDay;
if (days >= 14) {
Date d = new Date(timestampInMillis);
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
return fmt.format(d);
} else if (days > 0) {
return SeadroidApplication.getAppContext().getString(R.string.days_ago, days);
} else if (seconds >= 60 * 60) {
long hours = seconds / 3600;
return SeadroidApplication.getAppContext().getString(R.string.hours_ago, hours);
} else if (seconds >= 60) {
long minutes = seconds / 60;
return SeadroidApplication.getAppContext().getString(R.string.minutes_ago, minutes);
} else if (seconds > 0) {
return SeadroidApplication.getAppContext().getString(R.string.seconds_ago, seconds);
} else {
return SeadroidApplication.getAppContext().getString(R.string.just_now);
}
}
/**
* Translate create time
*/
public static String translateTime() {
long now = Calendar.getInstance().getTimeInMillis();
Date d = new Date(now);
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
return fmt.format(d);
}
public static long now() {
return Calendar.getInstance().getTimeInMillis();
}
public static String getFileMimeType(String path) {
String name = fileNameFromPath(path);
String suffix = name.substring(name.lastIndexOf('.') + 1).toLowerCase();
if (suffix.length() == 0) {
return MIME_APPLICATION_OCTET_STREAM;
} else {
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix);
if (mime != null) {
return mime;
} else {
return MIME_APPLICATION_OCTET_STREAM;
}
}
}
public static String getFileMimeType(File file) {
return getFileMimeType(file.getPath());
}
public static void copyFile(File src, File dst) throws IOException {
InputStream in = new BufferedInputStream(new FileInputStream(src));
OutputStream out = new BufferedOutputStream(new FileOutputStream(dst));
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
/************ MutiFileChooser ************/
private static Comparator<SelectableFile> mComparator = new Comparator<SelectableFile>() {
public int compare(SelectableFile f1, SelectableFile f2) {
// Sort alphabetically by lower case, which is much cleaner
return f1.getName().toLowerCase().compareTo(
f2.getName().toLowerCase());
}
};
private static FileFilter mFileFilter = new FileFilter() {
public boolean accept(File file) {
final String fileName = file.getName();
// Return files only (not directories) and skip hidden files
return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX);
}
};
private static FileFilter mDirFilter = new FileFilter() {
public boolean accept(File file) {
final String fileName = file.getName();
// Return directories only and skip hidden directories
return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX);
}
};
public static List<SelectableFile> getFileList(String path, List<File> selectedFile) {
ArrayList<SelectableFile> list = Lists.newArrayList();
// Current directory File instance
final SelectableFile pathDir = new SelectableFile(path);
// List file in this directory with the directory filter
final SelectableFile[] dirs = pathDir.listFiles(mDirFilter);
if (dirs != null) {
// Sort the folders alphabetically
Arrays.sort(dirs, mComparator);
// Add each folder to the File list for the list adapter
for (SelectableFile dir : dirs) list.add(dir);
}
// List file in this directory with the file filter
final SelectableFile[] files = pathDir.listFiles(mFileFilter);
if (files != null) {
// Sort the files alphabetically
Arrays.sort(files, mComparator);
// Add each file to the File list for the list adapter
for (SelectableFile file : files) {
if (selectedFile != null) {
if (selectedFile.contains(file.getFile())) {
file.setSelected(true);
}
}
list.add(file);
}
}
return list;
}
public static Intent createGetContentIntent() {
// Implicitly allow the user to select a particular kind of data
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// The MIME data type filter
intent.setType("*/*");
// Only return URIs that can be opened with ContentResolver
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Allow user to select multiple files
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
// only show local document providers
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
}
return intent;
}
public static String getFilenamefromUri(Context context, Uri uri) {
ContentResolver resolver =context.getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
String displayName = null;
if (cursor != null && cursor.moveToFirst()) {
// Note it's called "Display Name". This is
// provider-specific, and might not necessarily be the file name.
displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
cursor.close();
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
displayName = uri.getPath().replaceAll(".*/", "");
} else displayName = "unknown filename";
return displayName;
}
public static String getPath(Context context, Uri uri) throws URISyntaxException {
if ("content".equalsIgnoreCase(uri.getScheme())) {
String[] projection = { "_data" };
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
int column_index = cursor
.getColumnIndexOrThrow("_data");
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
} catch (Exception e) {
// Eat it
}
}
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getStackTrace(Exception e) {
StringWriter buffer = new StringWriter();
PrintWriter writer = new PrintWriter(buffer);
e.printStackTrace(writer);
return buffer.toString();
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromStream(InputStream stream,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(stream, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// return BitmapFactory.decodeResource(res, resId, options);
return BitmapFactory.decodeStream(stream, null, options);
}
public static String assembleUserName(String email, String server) {
if (email == null || server == null)
return null;
if (TextUtils.isEmpty(email) || TextUtils.isEmpty(server))
return "";
// strip port, like :8000 in 192.168.1.116:8000
if (server.indexOf(":") != -1)
server = server.substring(0, server.indexOf(':'));
String info = String.format("%s (%s)", email, server);
info = info.replaceAll("[^\\w\\d\\.@\\(\\) ]", "_");
return info;
}
public static void hideSoftKeyboard(View view) {
if (view == null)
return;
((InputMethodManager) SeadroidApplication.getAppContext().getSystemService(
Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
view.getWindowToken(), 0);
}
public static String cleanServerURL(String serverURL) throws MalformedURLException {
if (!serverURL.endsWith("/")) {
serverURL = serverURL + "/";
}
// XXX: android 4.0.3 ~ 4.0.4 can't handle urls with underscore (_) in the host field.
// See https://github.com/nostra13/Android-Universal-Image-Loader/issues/256 , and
// https://code.google.com/p/android/issues/detail?id=24924
//
new URL(serverURL); // will throw MalformedURLException if serverURL not valid
return serverURL;
}
public static List<ResolveInfo> getAppsByIntent(Intent intent) {
PackageManager pm = SeadroidApplication.getAppContext().getPackageManager();
List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
// Remove seafile app from the list
String seadroidPackageName = SeadroidApplication.getAppContext().getPackageName();
ResolveInfo info;
Iterator<ResolveInfo> iter = infos.iterator();
while (iter.hasNext()) {
info = iter.next();
if (info.activityInfo.packageName.equals(seadroidPackageName)) {
iter.remove();
}
}
return infos;
}
public final static boolean isValidEmail(CharSequence target) {
return !TextUtils.isEmpty(target) && android.util.Patterns.EMAIL_ADDRESS.matcher(target).matches();
}
private static long lastClickTime;
/**
* check if click event is a fast tapping
* @return
*/
public static boolean isFastTapping() {
long time = System.currentTimeMillis();
if (time - lastClickTime < 500) {
return true;
}
lastClickTime = time;
return false;
}
/**
* SslCertificate class does not has a public getter for the underlying
* X509Certificate, we can only do this by hack. This only works for andorid 4.0+
* @see https://groups.google.com/forum/#!topic/android-developers/eAPJ6b7mrmg
*/
public static X509Certificate getX509CertFromSslCertHack(SslCertificate sslCert) {
X509Certificate x509Certificate = null;
Bundle bundle = SslCertificate.saveState(sslCert);
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
} catch (CertificateException e) {
x509Certificate = null;
}
}
return x509Certificate;
}
public static boolean isSameCert(SslCertificate sslCert, X509Certificate x509Cert) {
if (sslCert == null || x509Cert == null) {
return false;
}
X509Certificate realCert = getX509CertFromSslCertHack(sslCert);
if (realCert != null) {
// for android 4.0+
return realCert.equals(x509Cert);
} else {
// for andorid < 4.0
return SslCertificateComparator.compare(sslCert,
new SslCertificate(x509Cert));
}
}
/**
* Compare SslCertificate objects for android before 4.0
*/
public static class SslCertificateComparator {
private SslCertificateComparator() {
}
public static boolean compare(SslCertificate cert1, SslCertificate cert2) {
return isSameDN(cert1.getIssuedTo(), cert2.getIssuedTo())
&& isSameDN(cert1.getIssuedBy(), cert2.getIssuedBy())
&& isSameDate(cert1.getValidNotBeforeDate(), cert2.getValidNotBeforeDate())
&& isSameDate(cert1.getValidNotAfterDate(), cert2.getValidNotAfterDate());
}
private static boolean isSameDate(Date date1, Date date2) {
if (date1 == null && date2 == null) {
return true;
} else if (date1 == null || date2 == null) {
return false;
}
return date1.equals(date2);
}
private static boolean isSameDN(SslCertificate.DName dName1, SslCertificate.DName dName2) {
if (dName1 == null && dName2 == null) {
return true;
} else if (dName1 == null || dName2 == null) {
return false;
}
return dName1.getDName().equals(dName2.getDName());
}
}
public static int dip2px(Context context, float dip) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dip * scale + 0.5f);
}
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}