/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.barrasso.android.volume.utils; import android.accessibilityservice.AccessibilityService; import android.annotation.TargetApi; import android.app.Activity; import android.app.WallpaperManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PictureDrawable; import android.graphics.drawable.StateListDrawable; import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.SystemClock; import android.renderscript.Allocation; import android.renderscript.Element; import android.renderscript.RenderScript; import android.renderscript.ScriptIntrinsicBlur; import android.service.notification.NotificationListenerService; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.provider.Settings; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.AnalogClock; import android.widget.ImageView; import android.widget.TextView; import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; import android.view.accessibility.AccessibilityEvent; import android.accessibilityservice.AccessibilityServiceInfo; import android.view.accessibility.AccessibilityManager; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.lang.reflect.Field; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import me.barrasso.android.volume.LogUtils; import me.barrasso.android.volume.VolumeAccessibilityService; import me.barrasso.android.volume.popup.BlackberryVolumePanel; import me.barrasso.android.volume.popup.HeadsUpVolumePanel; import me.barrasso.android.volume.popup.UberVolumePanel; import me.barrasso.android.volume.popup.WPVolumePanel; import static android.media.AudioManager.ADJUST_RAISE; import static android.media.AudioManager.ADJUST_SAME; import static me.barrasso.android.volume.LogUtils.LOGE; /** * Because every project needs a Utils class. */ public class Utils { private static final String TAG = LogUtils.makeLogTag(Utils.class); /*** * Android L (lollipop, API 21) introduced a new problem when trying to invoke implicit intent, * "java.lang.IllegalArgumentException: Service Intent must be explicit" * * If you are using an implicit intent, and know only 1 target would answer this intent, * This method will help you turn the implicit intent into the explicit form. * * Inspired from SO answer: http://stackoverflow.com/a/26318757/1446466 * @param context * @param implicitIntent - The original implicit intent * @return Explicit Intent created from the implicit original intent */ public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) { // Retrieve all services that can match the given intent PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); // Make sure only one match was found if (resolveInfo == null || resolveInfo.size() != 1) { return null; } // Get component info and create ComponentName ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); // Create a new intent. Use the old one for extras and such reuse Intent explicitIntent = new Intent(implicitIntent); // Set the component to be explicit explicitIntent.setComponent(component); return explicitIntent; } public static final long MILLIS_NANOS = 1000000l; // 1 ms = 1,000,000 nanos public static final int SECONDS_MILLIS = 1000; // 1 second is 1000 ms public static final int MINUTES_MILLIS = 60 * SECONDS_MILLIS; // 1 minute = 60 sec public static final int HOURS_MILLIS = 60 * MINUTES_MILLIS; // 1 hour = 60 min // From com.android.internal.util.ArrayUtils /** * Checks that value is present as at least one of the elements of the array. * * @param array the array to check in * @param value the value to check for * @return true if the value is present in the array */ public static <T> boolean contains(T[] array, T value) { for (T element : array) { if (element == null) { if (value == null) return true; } else { if (value != null && element.equals(value)) return true; } } return false; } public static boolean contains(int[] array, int value) { for (int element : array) { if (element == value) { return true; } } return false; } /** @return The length of all arrays if they match, -1 if else. */ public static int lengthsMatch(Object[]... arrays) { if (null == arrays || arrays.length == 0) return -1; int len = (arrays[0] == null) ? 0 : arrays[0].length; for (Object[] array : arrays) { int thisLen = (array == null) ? 0 : array.length; if (thisLen != len) return -1; } return len; } public static String[] getServiceComponentNames(Class<?> clazz) { return new String[]{ clazz.getPackage().getName() + '/' + clazz.getName(), clazz.getPackage().getName() + "/." + clazz.getSimpleName()}; } public static String[] getServiceComponentNames(Context context, String className) { final String packageName = context.getPackageName(); return new String[]{ packageName + '/' + packageName + '.' + className, packageName + "/." + className}; } /** @return True if a given {@link android.service.notification.NotificationListenerService} is enabled. */ public static <T extends NotificationListenerService> boolean isNotificationListenerServiceRunning(Context context, Class<T> clazz) { return isSettingsServiceEnabled(context, Constants.getEnabledNotificationListeners(), getServiceComponentNames(clazz)); } /** @return True if a given {@link android.service.notification.NotificationListenerService} is enabled. */ public static <T extends NotificationListenerService> boolean isNotificationListenerServiceRunning(Context context, String className) { return isSettingsServiceEnabled(context, Constants.getEnabledNotificationListeners(), getServiceComponentNames(context, className)); } public static <T extends AccessibilityService> boolean isAccessibilityServiceEnabled(Context context, Class<T> clazz) { return isAccessibilityServiceEnabled(context, getServiceComponentNames(clazz)); } /** * API 14+, check if the given {@link AccessibilityService} is enabled. * * @return True if id is an enabled {@link AccessibilityService}. */ public static boolean isAccessibilityServiceEnabled14(Context context, String[] ids) { AccessibilityManager aManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); if (null == aManager || null == ids) return false; List<AccessibilityServiceInfo> services = aManager .getEnabledAccessibilityServiceList(AccessibilityEvent.TYPES_ALL_MASK); for (AccessibilityServiceInfo service : services) { if (contains(ids, service.getId())) { return true; } } return false; } private static final TextUtils.SimpleStringSplitter COLON_SPLITTER = new TextUtils.SimpleStringSplitter(':'); /** * API 4+, check if the given {@link AccessibilityService} is enabled. * * @return True if id is an enabled {@link AccessibilityService}. */ public static boolean isAccessibilityServiceEnabled4(Context context, String[] ids) { return isSettingsServiceEnabled(context, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ids); } /** * API 4+, check if the given {@link AccessibilityService} is enabled. * * @return True if id is an enabled {@link AccessibilityService}. */ public static boolean isSettingsServiceEnabled(Context context, String setting, String[] ids) { // Check the list of system settings to see if a service is running. String eServices = Settings.Secure.getString(context.getContentResolver(), setting); if (!TextUtils.isEmpty(eServices) && null != ids) { TextUtils.SimpleStringSplitter splitter = COLON_SPLITTER; splitter.setString(eServices); while (splitter.hasNext()) { String aService = splitter.next(); if (contains(ids, aService)) { return true; } } } return false; } /** * @return True if id is an enabled {@link AccessibilityService}. */ public static boolean isAccessibilityServiceEnabled(Context context, String[] ids) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { return isAccessibilityServiceEnabled14(context, ids); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) { return isAccessibilityServiceEnabled4(context, ids); } return false; // Not supported } /** * @return True if system-wide Accessibility is enabled. */ public static boolean isAccessibilityEnabled(Context context) { // Also see Settings.Secure.ACCESSIBILITY_ENABLED, alternative method. AccessibilityManager aManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); return (null != aManager && aManager.isEnabled()); } public static boolean isMyServiceRunning(Context context, Class<?> serviceClass) { return isMyServiceRunning(context, serviceClass.getName()); } public static boolean isMyServiceRunning(Context context, String serviceClass) { if (null == serviceClass || null == context) return false; ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<RunningServiceInfo> services = manager.getRunningServices(Integer.MAX_VALUE); for (RunningServiceInfo service : services) { if (serviceClass.equals(service.service.getClassName())) { return true; } } return false; } public static Bitmap recolorBitmap(Drawable drawable, int color) { if (drawable == null) { return null; } int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); if (width <= 0 || height <= 0) { return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); } Bitmap outBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(outBitmap); drawable.setBounds(0, 0, outBitmap.getWidth(), outBitmap.getHeight()); drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); drawable.draw(canvas); drawable.setColorFilter(null); drawable.setCallback(null); // free up any references return outBitmap; } public static Drawable makeRecoloredDrawable(Context context, BitmapDrawable drawable, int color, boolean withStates) { Bitmap recoloredBitmap = recolorBitmap(drawable, color); BitmapDrawable recoloredDrawable = new BitmapDrawable( context.getResources(), recoloredBitmap); if (!withStates) { return recoloredDrawable; } StateListDrawable stateDrawable = new StateListDrawable(); stateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawable); stateDrawable.addState(new int[]{android.R.attr.state_focused}, drawable); stateDrawable.addState(new int[]{}, recoloredDrawable); return stateDrawable; } public static void traverseAndRecolor(View root, int color, boolean withStates, boolean setClickableItemBackgrounds) { Context context = root.getContext(); if (setClickableItemBackgrounds && root.isClickable()) { StateListDrawable selectableItemBackground = new StateListDrawable(); selectableItemBackground.addState(new int[]{android.R.attr.state_pressed}, new ColorDrawable((color & 0xffffff) | 0x33000000)); selectableItemBackground.addState(new int[]{android.R.attr.state_focused}, new ColorDrawable((color & 0xffffff) | 0x44000000)); selectableItemBackground.addState(new int[]{}, null); root.setBackground(selectableItemBackground); } if (root instanceof ViewGroup) { ViewGroup parent = (ViewGroup) root; for (int i = 0; i < parent.getChildCount(); i++) { traverseAndRecolor(parent.getChildAt(i), color, withStates, setClickableItemBackgrounds); } } else if (root instanceof ImageView) { ImageView imageView = (ImageView) root; Drawable sourceDrawable = imageView.getDrawable(); if (withStates && sourceDrawable != null && sourceDrawable instanceof BitmapDrawable) { imageView.setImageDrawable(makeRecoloredDrawable(context, (BitmapDrawable) sourceDrawable, color, true)); } else { imageView.setColorFilter(color, PorterDuff.Mode.MULTIPLY); } } else if (root instanceof TextView) { TextView textView = (TextView) root; if (withStates) { int sourceColor = textView.getCurrentTextColor(); ColorStateList colorStateList = new ColorStateList(new int[][]{ new int[]{android.R.attr.state_pressed}, new int[]{android.R.attr.state_focused}, new int[]{} }, new int[]{ sourceColor, sourceColor, color }); textView.setTextColor(colorStateList); } else { textView.setTextColor(color); } } else if (root instanceof AnalogClock) { AnalogClock analogClock = (AnalogClock) root; try { Field hourHandField = AnalogClock.class.getDeclaredField("mHourHand"); hourHandField.setAccessible(true); Field minuteHandField = AnalogClock.class.getDeclaredField("mMinuteHand"); minuteHandField.setAccessible(true); Field dialField = AnalogClock.class.getDeclaredField("mDial"); dialField.setAccessible(true); BitmapDrawable hourHand = (BitmapDrawable) hourHandField.get(analogClock); if (hourHand != null) { Drawable d = makeRecoloredDrawable(context, hourHand, color, withStates); d.setCallback(analogClock); hourHandField.set(analogClock, d); } BitmapDrawable minuteHand = (BitmapDrawable) minuteHandField.get(analogClock); if (minuteHand != null) { Drawable d = makeRecoloredDrawable(context, minuteHand, color, withStates); d.setCallback(analogClock); minuteHandField.set(analogClock, d); } BitmapDrawable dial = (BitmapDrawable) dialField.get(analogClock); if (dial != null) { Drawable d = makeRecoloredDrawable(context, dial, color, withStates); d.setCallback(analogClock); dialField.set(analogClock, d); } } catch (Throwable t) { LOGE(TAG, "Error recoloring View hierarchy.", t); } } } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and barHeight 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 barHeight larger than the requested height and barHeight. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } // http://stackoverflow.com/questions/13557195/how-to-check-if-string-is-a-valid-class-identifier public static boolean isFullyQualifiedClassname(final String classname) { if (classname == null) return false; String[] parts = classname.split("[\\.]"); if (parts.length == 0) return false; for (String part : parts) { CharacterIterator iter = new StringCharacterIterator(part); // Check first character (there should at least be one character for each part) ... char c = iter.first(); if (c == CharacterIterator.DONE) return false; if (!Character.isJavaIdentifierStart(c) && !Character.isIdentifierIgnorable(c)) return false; c = iter.next(); // Check the remaining characters, if there are any ... while (c != CharacterIterator.DONE) { if (!Character.isJavaIdentifierPart(c) && !Character.isIdentifierIgnorable(c)) return false; c = iter.next(); } } return true; } /** * Creates a new IntentFilter, the product of both inputs. */ public static IntentFilter merge(IntentFilter a, IntentFilter b) { IntentFilter both = new IntentFilter(a); for (int i = 0, e = b.countActions(); i < e; ++i) both.addAction(b.getAction(i)); return both; } public static String bundle2string(Bundle bundle) { if (null == bundle) return "[Bundle null]"; String string = "[Bundle: "; for (String key : bundle.keySet()) { string += key + "=" + bundle.get(key) + " "; } string += "]"; return string; } /** * @return The package name for an app with a given PID. */ public static String getPackageName(ActivityManager am, PackageManager pm, final int pID) { List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo process : processes) { try { if (process.pid == pID) { ApplicationInfo info = pm.getApplicationInfo(process.processName, PackageManager.GET_META_DATA); return info.packageName; } } catch (Throwable e) { LOGE(TAG, "Error checking process information.", e); } } return null; } /** * @return True if a {@link android.view.KeyEvent} corresponds to a media action. */ public static boolean isMediaKeyCode(final int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_CLOSE: case KeyEvent.KEYCODE_MEDIA_EJECT: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: case KeyEvent.KEYCODE_MEDIA_STOP: case KeyEvent.KEYCODE_MEDIA_RECORD: case KeyEvent.KEYCODE_MEDIA_REWIND: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: return true; } return false; } /** * @return True if a {@link android.view.KeyEvent} corresponds to a volume button. */ public static boolean isVolumeKeyCode(final int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: return true; } return false; } /** @return The next ringer mode according to the direction of the change. */ public static int nextRingerMode(final int direction, final int mRingerMode, final boolean hasVibrator) { if (direction == ADJUST_SAME) return mRingerMode; int newRingerMode = mRingerMode; if (hasVibrator) { if (direction == ADJUST_RAISE) { switch (mRingerMode) { case AudioManager.RINGER_MODE_VIBRATE: newRingerMode = AudioManager.RINGER_MODE_NORMAL; break; case AudioManager.RINGER_MODE_SILENT: newRingerMode = AudioManager.RINGER_MODE_VIBRATE; break; case AudioManager.RINGER_MODE_NORMAL: newRingerMode = AudioManager.RINGER_MODE_SILENT; break; } } else { switch (mRingerMode) { case AudioManager.RINGER_MODE_VIBRATE: newRingerMode = AudioManager.RINGER_MODE_SILENT; break; case AudioManager.RINGER_MODE_SILENT: newRingerMode = AudioManager.RINGER_MODE_NORMAL; break; case AudioManager.RINGER_MODE_NORMAL: newRingerMode = AudioManager.RINGER_MODE_VIBRATE; break; } } } else { // Without a vibrator, it's a binary state. switch (mRingerMode) { case AudioManager.RINGER_MODE_SILENT: newRingerMode = AudioManager.RINGER_MODE_NORMAL; break; case AudioManager.RINGER_MODE_NORMAL: newRingerMode = AudioManager.RINGER_MODE_SILENT; break; } } return newRingerMode; } /** * @return The {@link Bitmap} for the current wallpaper, or null if none exists. */ public static Bitmap getWallpaperBitmap(WallpaperManager wManager, Context mContext) { if (null == wManager || null == mContext) return null; final Bitmap wallpaper = getCurrentWallpaperLocked(wManager, mContext); if (null != wallpaper) return wallpaper; final Drawable wallpaperD = wManager.getDrawable(); if (null != wallpaperD) { return drawableToBitmap(wallpaperD); } return null; } /** * @returns A {@link Bitmap} for a {@link Drawable}. */ public static Bitmap drawableToBitmap(final Drawable drawable) { if (null == drawable) return null; if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } final Bitmap bitmap = Bitmap.createBitmap(Math.max(0, drawable.getIntrinsicWidth()), Math.max(0, drawable.getIntrinsicHeight()), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); // PictureDrawable's get handled separately. if (drawable instanceof PictureDrawable) { canvas.drawPicture(((PictureDrawable) drawable).getPicture()); return bitmap; } drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } /** * @see android.app.WallpaperManager$Globals#getCurrentWallpaperLocked(Context) */ public static Bitmap getCurrentWallpaperLocked(WallpaperManager wManager, Context mContext) { if (null == wManager) return null; final String GLOBALS = "Globals"; try { // Find: android.app.WallpaperManager$Globals final Class<?>[] sClasses = wManager.getClass().getDeclaredClasses(); for (final Class<?> sClass : sClasses) { final String sName = sClass.getSimpleName(); if (TextUtils.isEmpty(sName)) continue; if (GLOBALS.equals(sName)) { // Invoke the default constructor, Globals(Looper) final Constructor<?> sConstructor = sClass.getDeclaredConstructor(Looper.class); if (null != sConstructor) { sConstructor.setAccessible(true); final Object sGlobals = sConstructor.newInstance(mContext.getMainLooper()); // Call the getCurrentWallpaperLocked() method on our Globals Object. final Method getBitmap = sClass.getDeclaredMethod("getCurrentWallpaperLocked", Context.class); if (null != getBitmap) { // It's private, but who cares? getBitmap.setAccessible(true); return (Bitmap) getBitmap.invoke(sGlobals, mContext); } } } } } catch (Throwable t) { LOGE(TAG, "Cannot call WallpaperManager#getCurrentWallpaperLocked", t); } return null; } private static RenderScript rs; /** * Applies a blur to a {@link Bitmap}. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static Bitmap fastBlur(Context mContext, Bitmap sentBitmap, final int radius) { if (null == rs) { rs = RenderScript.create(mContext); } final Allocation in = Allocation.createFromBitmap(rs, sentBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); final Allocation out = Allocation.createTyped(rs, in.getType()); final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); script.setRadius(radius); script.setInput(in); script.forEach(out); out.copyTo(sentBitmap); return (sentBitmap); } /** @return The Bitmap for a View hierarchy. */ public static Bitmap loadBitmapFromView(View v) { v.clearFocus(); v.setPressed(false); if (v.getMeasuredHeight() <= 0) { v.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); Bitmap b = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); v.draw(c); return b; } Bitmap b = Bitmap.createBitmap( v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); Drawable bgDrawable = v.getBackground(); if (bgDrawable != null) bgDrawable.draw(c); v.draw(c); return b; } public static Bitmap loadBitmapFromViewCache(View v) { v.clearFocus(); v.setPressed(false); boolean willNotCache = v.willNotCacheDrawing(); v.setWillNotCacheDrawing(false); // Reset the drawing cache background color to fully transparent // for the duration of this operation int color = v.getDrawingCacheBackgroundColor(); v.setDrawingCacheBackgroundColor(0); if (color != 0) { v.destroyDrawingCache(); } v.buildDrawingCache(); Bitmap cacheBitmap = v.getDrawingCache(); if (null == cacheBitmap) return null; Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); // Restore the view v.destroyDrawingCache(); v.setWillNotCacheDrawing(willNotCache); v.setDrawingCacheBackgroundColor(color); return bitmap; } // Staticlly initialized variables for better performance (but more memory usage). private static final Paint monochromePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private static final ColorMatrix monochromeMatrix = new ColorMatrix(); private static final ColorMatrixColorFilter monochromeFilter; static { monochromeMatrix.setSaturation(0); monochromePaint.setAntiAlias(true); monochromeFilter = new ColorMatrixColorFilter(monochromeMatrix); monochromePaint.setColorFilter(monochromeFilter); } /** Makes a {@link Bitmap} monochrome. */ public static Bitmap monochrome(Bitmap sentBitmap) { Bitmap bmpMonochrome = Bitmap.createBitmap( sentBitmap.getWidth(), sentBitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmpMonochrome); canvas.drawBitmap(sentBitmap, 0, 0, monochromePaint); return bmpMonochrome; } /** Masks a {@link android.graphics.Bitmap}. */ public static Bitmap mask(Bitmap original, Bitmap mask) { Bitmap result = Bitmap.createBitmap(mask.getWidth(), mask.getHeight(), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(result); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); c.drawBitmap(original,0,0,null); c.drawBitmap(mask,0,0,paint); paint.setXfermode(null); return result; } public static void tap(View v) { int[] xy = new int[2]; v.getLocationOnScreen(xy); final int viewWidth = v.getWidth(); final int viewHeight = v.getHeight(); final float x = xy[0] + (viewWidth / 2.0f); float y = xy[1] + (viewHeight / 2.0f); long downTime = SystemClock.uptimeMillis(); long eventTime = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); if (v instanceof ViewGroup) ((ViewGroup) v).dispatchTouchEvent(event); else v.dispatchGenericMotionEvent(event); eventTime = SystemClock.uptimeMillis(); final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop(); event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0); if (v instanceof ViewGroup) ((ViewGroup) v).dispatchTouchEvent(event); else v.dispatchGenericMotionEvent(event); eventTime = SystemClock.uptimeMillis(); event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); if (v instanceof ViewGroup) ((ViewGroup) v).dispatchTouchEvent(event); else v.dispatchGenericMotionEvent(event); } /** * Gets the list of available media receivers. * @return The list of {@code ResolveInfo} for different media button receivers. */ public static List<ResolveInfo> getMediaReceivers(PackageManager packageManager) { if (null == packageManager) return new ArrayList<ResolveInfo>(0); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); return packageManager.queryBroadcastReceivers(mediaButtonIntent, PackageManager.GET_INTENT_FILTERS | PackageManager.GET_RESOLVED_FILTER); } public static Set<String> getPackageNames(List<ResolveInfo> infos) { Set<String> strs = new HashSet<String>(); if (null == infos) return strs; for (ResolveInfo info : infos) strs.add(info.resolvePackageName); return strs; } /** @return True if the device has phone capabilities. */ public static boolean hasTelephone(Context context) { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); return (telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE); } /** @return The timestamp of the last compilation/ build. */ public static long lastBuildTime(Context context) { ZipFile zf = null; try{ zf = new ZipFile(context.getPackageCodePath()); ZipEntry ze = zf.getEntry("classes.dex"); return ze.getTime(); } catch(Throwable e) { LOGE(TAG, "Error obtaining build timestamp.", e); } finally { try { if (null != zf) zf.close(); } catch (IOException ioe) { LOGE(TAG, "Error closing " + ZipFile.class.getSimpleName() + ".", ioe); } } return Long.MIN_VALUE; } /** @return A timestamp hash of the last build date. */ public static CharSequence lastBuildTimestamp(Context context) { long time = lastBuildTime(context); if (time == Long.MIN_VALUE) return null; return pad(String.valueOf(time)); } public static String pad(String s) { final String source = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; final String target = "Q9A8ZWS7XEDC6RFVT5GBY4HNU3J2MI1KO0LP"; char[] result = new char[s.length()]; for (int i = 0; i < result.length; i++) { int index = source.indexOf(s.charAt(i)); result[i] = target.charAt(index); } return new String(result); } public static String upad(String s) { final String source = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; final String target = "Q9A8ZWS7XEDC6RFVT5GBY4HNU3J2MI1KO0LP"; char[] result = new char[s.length()]; for (int i = 0; i < result.length; i++) { int index = target.indexOf(s.charAt(i)); result[i] = source.charAt(index); } return new String(result); } public static boolean isPackageInstalled(Context context, String pack) { if (null == context || TextUtils.isEmpty(pack)) return false; try { PackageInfo info = context.getPackageManager().getPackageInfo(pack, 0); return (info.packageName.equals(pack)); } catch (PackageManager.NameNotFoundException nfe) { LOGE("Utils", "Could not find package: " + pack); return false; } } /** * {@link java.lang.Enum} representing the installer of an app. * TODO: update this structure with other services, if applicable. */ public static enum Installer { UNKNOWN(null, "https://play.google.com/store/apps/details?id="), GOOGLE_PLAY("com.android.vending", "market://details?id="), AMAZON("com.amazon.venezia", "http://www.amazon.com/gp/mas/dl/android?p="); public String getPackageName() { return packageName; } public Intent getRateIntent(String appPackageName) { Intent launch = new Intent(Intent.ACTION_VIEW, Uri.parse(viewUrl + appPackageName)); launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return launch; } private String packageName; private String viewUrl; Installer(String packageName, String viewUrl) { this.packageName = packageName; this.viewUrl = viewUrl; } } /** @return The {@link me.barrasso.android.volume.utils.Utils.Installer} of this application. */ public static Installer getInstaller(Context context) { if (("amazon").equals(Build.MANUFACTURER.toLowerCase(Locale.US))) return Installer.AMAZON; PackageManager packageManager = context.getPackageManager(); String packageName = context.getPackageName(); String installerPackageName = packageManager.getInstallerPackageName(packageName); if (!TextUtils.isEmpty(installerPackageName)) for (Installer installer : Installer.values()) if (installerPackageName.equals(installer.getPackageName())) return installer; return Installer.UNKNOWN; } /** Launch the market-installed rate Activity with a default web URL. */ public static boolean launchMarketActivity(Context context) { Installer installer = getInstaller(context); try { context.startActivity(installer.getRateIntent(context.getPackageName())); return true; } catch (ActivityNotFoundException ane) { LOGE(TAG, "Could not launch Market rate Activity.", ane); return false; } } public static void unbindDrawables(View view) { if (null == view) return; if (view.getBackground() != null) { view.getBackground().setCallback(null); view.setBackground(null); } if (view instanceof ViewGroup) { ViewGroup container = (ViewGroup) view; for (int i = 0, e = container.getChildCount(); i < e; i++) { unbindDrawables(container.getChildAt(i)); } container.removeAllViews(); } else if (view instanceof ImageView) { ImageView image = (ImageView) view; Drawable d = image.getDrawable(); if (d instanceof BitmapDrawable) { Bitmap bmp = ((BitmapDrawable) d).getBitmap(); if (null != bmp) { bmp.recycle(); } } image.setImageDrawable(null); } } static final Object mScreenshotLock = new Object(); static ServiceConnection mScreenshotConnection = null; // Taken from PhoneWindowManager system service // Assume this is called from the Handler thread. public static void takeScreenshot(final Context mContext, final Handler mHandler) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; } final Runnable mScreenshotTimeout = new Runnable() { @Override public void run() { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; } } } }; ComponentName cn = new ComponentName("com.android.systemui", "com.android.systemui.screenshot.TakeScreenshotService"); Intent intent = new Intent(); intent.setComponent(cn); ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mScreenshotLock) { if (mScreenshotConnection != this) { return; } Messenger messenger = new Messenger(service); Message msg = Message.obtain(null, 1); final ServiceConnection myConn = this; Handler h = new Handler(mHandler.getLooper()) { @Override public void handleMessage(Message msg) { synchronized (mScreenshotLock) { if (mScreenshotConnection == myConn) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; mHandler.removeCallbacks(mScreenshotTimeout); } } } }; msg.replyTo = new Messenger(h); msg.arg1 = msg.arg2 = 0; try { messenger.send(msg); } catch (RemoteException e) { LOGE(TAG, "Error sending reply message for screenshot.", e); } } } @Override public void onServiceDisconnected(ComponentName name) {} }; if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) { mScreenshotConnection = conn; mHandler.postDelayed(mScreenshotTimeout, 10000); } } } public static String getPreferencesString(SharedPreferences prefs) { if (null == prefs) return ""; Map<String,?> keys = prefs.getAll(); StringBuilder allPrefs = new StringBuilder(); for(Map.Entry<String,?> entry : keys.entrySet()) { allPrefs.append(entry.getKey()); allPrefs.append('='); allPrefs.append(String.valueOf(entry.getValue())); allPrefs.append('\n'); } return allPrefs.toString(); } public static boolean supportsMediaPlayback(String name) { return (WPVolumePanel.class.getSimpleName().equals(name) || HeadsUpVolumePanel.class.getSimpleName().equals(name)) || UberVolumePanel.class.getSimpleName().equals(name) || BlackberryVolumePanel.class.getSimpleName().equals(name); } // API Compatibility private static final boolean HAS_MEDIA_CONTROLLER; static { boolean hasMCS = false; try { Class<?> clazz = Class.forName("me.barrasso.android.volume.MediaControllerService"); hasMCS = (clazz != null); } catch (Throwable t) { hasMCS = false; } HAS_MEDIA_CONTROLLER = hasMCS; } /** API-Compatible version of accessing MediaControllerService's state. */ public static boolean isMediaControllerRunning(Context context) { return (HAS_MEDIA_CONTROLLER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isMyServiceRunning(context, "me.barrasso.android.volume.MediaControllerService")); } /** API-Compatible version of accessing MediaControllerService's external state (enabled). */ public static boolean isMediaControllerEnabled(Context context) { return (HAS_MEDIA_CONTROLLER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isNotificationListenerServiceRunning(context, "MediaControllerService")); } public static long[] parseVibrate(String line) { if (TextUtils.isEmpty(line)) return new long[0]; long[] vibTimes; String[] vibs = line.split("[,]"); vibTimes = new long[vibs.length]; int k = -1; for (String vib : vibs) vibTimes[++k] = Long.parseLong(vib); return vibTimes; } // Thanks Llama public static boolean HasStupidMenuButtonInBottomRight() { return Build.MODEL.equals("PadFone 2"); } public static boolean IsPileOfShitWhenDealingWithVibrateMode() { String str = Build.MODEL; return (str.equals("GT-I9500")) || (str.equals("SHV-E300K")) || (str.equals("SHV-E300L")) || (str.equals("SHV-E300S")) || (str.equals("GT-I9505")) || (str.equals("SGH-I337")) || (str.equals("SGH-M919")) || (str.equals("SCH-I545")) || (str.equals("SPH-L720")) || (str.equals("SCH-R970")) || (str.equals("GT-I9508")) || (str.equals("SCH-I959")) || (str.equals("GT-I9502")) || (str.equals("SGH-N045")) || (str.equals("SAMSUNG-SGH-I337")); } public static boolean HasChronicallyStupidWayToSetPriorityModeWhenItShouldBeSilentMode() { return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); } private static final String APP_DETAILS_CLASS_NAME = "com.android.settings.InstalledAppDetails"; private static final String APP_DETAILS_PACKAGE_NAME = "com.android.settings"; private static final String APP_PKG_NAME_21 = "com.android.settings.ApplicationPkgName"; private static final String APP_PKG_NAME_22 = "pkg"; private static final String SCHEME = "package"; public static void showInstalledAppDetails(Activity activity, String packageName) { Intent localIntent = new Intent(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { localIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); localIntent.setData(Uri.fromParts(SCHEME, packageName, null)); activity.startActivity(localIntent); } else { localIntent.setAction(Intent.ACTION_VIEW); localIntent.setClassName(APP_DETAILS_PACKAGE_NAME, APP_DETAILS_CLASS_NAME); localIntent.putExtra(APP_PKG_NAME_21, packageName); localIntent.putExtra(APP_PKG_NAME_22, packageName); } } }