You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
15 KiB
441 lines
15 KiB
/* |
|
* Copyright 2014 A.C.R. Development |
|
*/ |
|
package org.purplei2p.lightning.utils; |
|
|
|
import android.annotation.SuppressLint; |
|
import android.app.Activity; |
|
import android.content.Context; |
|
import android.content.DialogInterface; |
|
import android.content.Intent; |
|
import android.content.pm.ApplicationInfo; |
|
import android.content.pm.PackageManager; |
|
import android.content.res.Resources; |
|
import android.database.Cursor; |
|
import android.graphics.Bitmap; |
|
import android.graphics.BitmapFactory; |
|
import android.graphics.Canvas; |
|
import android.graphics.Color; |
|
import android.graphics.LinearGradient; |
|
import android.graphics.Paint; |
|
import android.graphics.Path; |
|
import android.graphics.Shader; |
|
import android.net.Uri; |
|
import android.os.Build; |
|
import android.os.Environment; |
|
import android.support.annotation.NonNull; |
|
import android.support.annotation.Nullable; |
|
import android.support.annotation.StringRes; |
|
import android.support.design.widget.Snackbar; |
|
import android.support.v7.app.AlertDialog; |
|
import android.text.TextUtils; |
|
import android.util.DisplayMetrics; |
|
import android.util.Log; |
|
import android.view.View; |
|
import android.webkit.URLUtil; |
|
import android.widget.Toast; |
|
|
|
import java.io.Closeable; |
|
import java.io.File; |
|
import java.io.IOException; |
|
import java.net.URI; |
|
import java.net.URISyntaxException; |
|
import java.text.SimpleDateFormat; |
|
import java.util.Date; |
|
|
|
import org.purplei2p.lightning.R; |
|
import org.purplei2p.lightning.MainActivity; |
|
import org.purplei2p.lightning.constant.Constants; |
|
import org.purplei2p.lightning.database.HistoryItem; |
|
import org.purplei2p.lightning.dialog.BrowserDialog; |
|
|
|
public final class Utils { |
|
|
|
private static final String TAG = "Utils"; |
|
|
|
public static boolean doesSupportHeaders() { |
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; |
|
} |
|
|
|
/** |
|
* Creates a new intent that can launch the email |
|
* app with a subject, address, body, and cc. It |
|
* is used to handle mail:to links. |
|
* |
|
* @param address the address to send the email to. |
|
* @param subject the subject of the email. |
|
* @param body the body of the email. |
|
* @param cc extra addresses to CC. |
|
* @return a valid intent. |
|
*/ |
|
@NonNull |
|
public static Intent newEmailIntent(String address, String subject, |
|
String body, String cc) { |
|
Intent intent = new Intent(Intent.ACTION_SEND); |
|
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{address}); |
|
intent.putExtra(Intent.EXTRA_TEXT, body); |
|
intent.putExtra(Intent.EXTRA_SUBJECT, subject); |
|
intent.putExtra(Intent.EXTRA_CC, cc); |
|
intent.setType("message/rfc822"); |
|
return intent; |
|
} |
|
|
|
/** |
|
* Creates a dialog with only a title, message, and okay button. |
|
* |
|
* @param activity the activity needed to create a dialog. |
|
* @param title the title of the dialog. |
|
* @param message the message of the dialog. |
|
*/ |
|
public static void createInformativeDialog(@NonNull Activity activity, @StringRes int title, @StringRes int message) { |
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity); |
|
builder.setTitle(title); |
|
builder.setMessage(message) |
|
.setCancelable(true) |
|
.setPositiveButton(activity.getResources().getString(R.string.action_ok), |
|
new DialogInterface.OnClickListener() { |
|
@Override |
|
public void onClick(DialogInterface dialog, int id) { |
|
} |
|
}); |
|
AlertDialog alert = builder.create(); |
|
alert.show(); |
|
BrowserDialog.setDialogSize(activity, alert); |
|
} |
|
|
|
/** |
|
* Displays a snackbar to the user with a String resource. |
|
* <p> |
|
* NOTE: If there is an accessibility manager enabled on |
|
* the device, such as LastPass, then the snackbar animations |
|
* will not work. |
|
* |
|
* @param activity the activity needed to create a snackbar. |
|
* @param resource the string resource to show to the user. |
|
*/ |
|
public static void showSnackbar(@NonNull Activity activity, @StringRes int resource) { |
|
View view = activity.findViewById(android.R.id.content); |
|
if (view == null) { |
|
Log.e(TAG, "showSnackbar", new NullPointerException("Unable to find android.R.id.content")); |
|
return; |
|
} |
|
Snackbar.make(view, resource, Snackbar.LENGTH_SHORT).show(); |
|
} |
|
|
|
/** |
|
* Displays a snackbar to the user with a string message. |
|
* <p> |
|
* NOTE: If there is an accessibility manager enabled on |
|
* the device, such as LastPass, then the snackbar animations |
|
* will not work. |
|
* |
|
* @param activity the activity needed to create a snackbar. |
|
* @param message the string message to show to the user. |
|
*/ |
|
public static void showSnackbar(@NonNull Activity activity, @NonNull String message) { |
|
View view = activity.findViewById(android.R.id.content); |
|
if (view == null) { |
|
Log.e(TAG, "showSnackbar", new NullPointerException("Unable to find android.R.id.content")); |
|
return; |
|
} |
|
Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show(); |
|
} |
|
|
|
/** |
|
* Shows a toast to the user. |
|
* Should only be used if an activity is |
|
* not available to show a snackbar. |
|
* |
|
* @param context the context needed to show the toast. |
|
* @param resource the string shown by the toast to the user. |
|
*/ |
|
public static void showToast(@NonNull Context context, @StringRes int resource) { |
|
Toast.makeText(context, resource, Toast.LENGTH_SHORT).show(); |
|
} |
|
|
|
/** |
|
* Converts Density Pixels (DP) to Pixels (PX). |
|
* |
|
* @param dp the number of density pixels to convert. |
|
* @return the number of pixels that the conversion generates. |
|
*/ |
|
public static int dpToPx(float dp) { |
|
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); |
|
return (int) (dp * metrics.density + 0.5f); |
|
} |
|
|
|
/** |
|
* Extracts the domain name from a URL. |
|
* |
|
* @param url the URL to extract the domain from. |
|
* @return the domain name, or the URL if the domain |
|
* could not be extracted. The domain name may include |
|
* HTTPS if the URL is an SSL supported URL. |
|
*/ |
|
@Nullable |
|
public static String getDomainName(@Nullable String url) { |
|
return getDomainName(url, false); |
|
} |
|
|
|
/** |
|
* Extracts the domain name from a URL. |
|
* |
|
* @param url the URL to extract the domain from. |
|
* @param domainonly flag to return only domain without scheme. |
|
* @return the domain name, or the URL if the domain |
|
* could not be extracted. The domain name may include |
|
* HTTPS if the URL is an SSL supported URL and domainonly is false. |
|
*/ |
|
@Nullable |
|
public static String getDomainName(@Nullable String url, boolean domainonly) { |
|
if (url == null || url.isEmpty()) return ""; |
|
|
|
boolean ssl = URLUtil.isHttpsUrl(url); |
|
int index = url.indexOf('/', 8); |
|
if (index != -1) { |
|
url = url.substring(0, index); |
|
} |
|
|
|
URI uri; |
|
String domain; |
|
try { |
|
uri = new URI(url); |
|
domain = uri.getHost(); |
|
} catch (URISyntaxException e) { |
|
e.printStackTrace(); |
|
domain = null; |
|
} |
|
|
|
if (domain == null || domain.isEmpty()) { |
|
return url; |
|
} |
|
if (ssl && !domainonly) |
|
return Constants.HTTPS + domain; |
|
else |
|
return domain.startsWith("www.") ? domain.substring(4) : domain; |
|
} |
|
|
|
public static void trimCache(@NonNull Context context) { |
|
try { |
|
File dir = context.getCacheDir(); |
|
|
|
if (dir != null && dir.isDirectory()) { |
|
deleteDir(dir); |
|
} |
|
} catch (Exception ignored) { |
|
|
|
} |
|
} |
|
|
|
private static boolean deleteDir(@Nullable File dir) { |
|
if (dir != null && dir.isDirectory()) { |
|
String[] children = dir.list(); |
|
for (String aChildren : children) { |
|
boolean success = deleteDir(new File(dir, aChildren)); |
|
if (!success) { |
|
return false; |
|
} |
|
} |
|
} |
|
// The directory is now empty so delete it |
|
return dir != null && dir.delete(); |
|
} |
|
|
|
/** |
|
* Creates and returns a new favicon which is the same as the provided |
|
* favicon but with horizontal or vertical padding of 4dp |
|
* |
|
* @param bitmap is the bitmap to pad. |
|
* @return the padded bitmap. |
|
*/ |
|
@NonNull |
|
public static Bitmap padFavicon(@NonNull Bitmap bitmap) { |
|
int padding = Utils.dpToPx(4); |
|
|
|
Bitmap paddedBitmap = Bitmap.createBitmap(bitmap.getWidth() + padding, bitmap.getHeight() |
|
+ padding, Bitmap.Config.ARGB_8888); |
|
|
|
Canvas canvas = new Canvas(paddedBitmap); |
|
canvas.drawARGB(0x00, 0x00, 0x00, 0x00); // this represents white color |
|
canvas.drawBitmap(bitmap, padding / 2, padding / 2, new Paint(Paint.FILTER_BITMAP_FLAG)); |
|
|
|
return paddedBitmap; |
|
} |
|
|
|
public static boolean isColorTooDark(int color) { |
|
final byte RED_CHANNEL = 16; |
|
final byte GREEN_CHANNEL = 8; |
|
//final byte BLUE_CHANNEL = 0; |
|
|
|
int r = ((int) ((float) (color >> RED_CHANNEL & 0xff) * 0.3f)) & 0xff; |
|
int g = ((int) ((float) (color >> GREEN_CHANNEL & 0xff) * 0.59)) & 0xff; |
|
int b = ((int) ((float) (color /* >> BLUE_CHANNEL */ & 0xff) * 0.11)) & 0xff; |
|
int gr = (r + g + b) & 0xff; |
|
int gray = gr /* << BLUE_CHANNEL */ + (gr << GREEN_CHANNEL) + (gr << RED_CHANNEL); |
|
|
|
return gray < 0x727272; |
|
} |
|
|
|
public static int mixTwoColors(int color1, int color2, float amount) { |
|
final byte ALPHA_CHANNEL = 24; |
|
final byte RED_CHANNEL = 16; |
|
final byte GREEN_CHANNEL = 8; |
|
//final byte BLUE_CHANNEL = 0; |
|
|
|
final float inverseAmount = 1.0f - amount; |
|
|
|
int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) + ((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff; |
|
int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) + ((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff; |
|
int b = ((int) (((float) (color1 & 0xff) * amount) + ((float) (color2 & 0xff) * inverseAmount))) & 0xff; |
|
|
|
return 0xff << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b; |
|
} |
|
|
|
@SuppressLint("SimpleDateFormat") |
|
public static File createImageFile() throws IOException { |
|
// Create an image file name |
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); |
|
String imageFileName = "JPEG_" + timeStamp + '_'; |
|
File storageDir = Environment |
|
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); |
|
return File.createTempFile(imageFileName, /* prefix */ |
|
".jpg", /* suffix */ |
|
storageDir /* directory */ |
|
); |
|
} |
|
|
|
/** |
|
* Quietly closes a closeable object like an InputStream or OutputStream without |
|
* throwing any errors or requiring you do do any checks. |
|
* |
|
* @param closeable the object to close |
|
*/ |
|
public static void close(@Nullable Closeable closeable) { |
|
if (closeable == null) |
|
return; |
|
try { |
|
closeable.close(); |
|
} catch (IOException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
/** |
|
* Utility method to close cursors. Cursor did not |
|
* implement Closeable until API 16, so using this |
|
* method for when we want to close a cursor. |
|
* |
|
* @param cursor the cursor to close |
|
*/ |
|
public static void close(@Nullable Cursor cursor) { |
|
if (cursor == null) { |
|
return; |
|
} |
|
try { |
|
cursor.close(); |
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
/** |
|
* Draws the trapezoid background for the horizontal tabs on a canvas object using |
|
* the specified color. |
|
* |
|
* @param canvas the canvas to draw upon |
|
* @param color the color to use to draw the tab |
|
*/ |
|
public static void drawTrapezoid(@NonNull Canvas canvas, int color, boolean withShader) { |
|
|
|
Paint paint = new Paint(); |
|
paint.setColor(color); |
|
paint.setStyle(Paint.Style.FILL); |
|
// paint.setFilterBitmap(true); |
|
paint.setAntiAlias(true); |
|
paint.setDither(true); |
|
if (withShader) { |
|
paint.setShader(new LinearGradient(0, 0.9f * canvas.getHeight(), |
|
0, canvas.getHeight(), |
|
color, mixTwoColors(Color.BLACK, color, 0.5f), |
|
Shader.TileMode.CLAMP)); |
|
} else { |
|
paint.setShader(null); |
|
} |
|
int width = canvas.getWidth(); |
|
int height = canvas.getHeight(); |
|
double radians = Math.PI / 3; |
|
int base = (int) (height / Math.tan(radians)); |
|
|
|
Path wallpath = new Path(); |
|
wallpath.reset(); |
|
wallpath.moveTo(0, height); |
|
wallpath.lineTo(width, height); |
|
wallpath.lineTo(width - base, 0); |
|
wallpath.lineTo(base, 0); |
|
wallpath.close(); |
|
|
|
canvas.drawPath(wallpath, paint); |
|
} |
|
|
|
/** |
|
* Creates a shortcut on the homescreen using the |
|
* {@link HistoryItem} information that opens the |
|
* browser. The icon, URL, and title are used in |
|
* the creation of the shortcut. |
|
* |
|
* @param activity the activity needed to create |
|
* the intent and show a snackbar message |
|
* @param item the HistoryItem to create the shortcut from |
|
*/ |
|
public static void createShortcut(@NonNull Activity activity, @NonNull HistoryItem item) { |
|
if (TextUtils.isEmpty(item.getUrl())) { |
|
return; |
|
} |
|
Log.d(TAG, "Creating shortcut: " + item.getTitle() + ' ' + item.getUrl()); |
|
Intent shortcutIntent = new Intent(activity, MainActivity.class); |
|
shortcutIntent.setData(Uri.parse(item.getUrl())); |
|
|
|
final String title = TextUtils.isEmpty(item.getTitle()) ? activity.getString(R.string.untitled) : item.getTitle(); |
|
|
|
Intent addIntent = new Intent(); |
|
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); |
|
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); |
|
addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, item.getBitmap()); |
|
addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); |
|
activity.sendBroadcast(addIntent); |
|
Utils.showSnackbar(activity, R.string.message_added_to_homescreen); |
|
} |
|
|
|
public static int calculateInSampleSize(@NonNull 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; |
|
} |
|
|
|
@Nullable |
|
public static String guessFileExtension(@NonNull String filename) { |
|
int lastIndex = filename.lastIndexOf('.') + 1; |
|
if (lastIndex > 0 && filename.length() > lastIndex) { |
|
return filename.substring(lastIndex, filename.length()); |
|
} |
|
return null; |
|
} |
|
|
|
}
|
|
|