/*
* 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 com.google.android.apps.dashclock;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
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.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AnalogClock;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.apps.dashclock.api.ExtensionData;
import com.google.android.apps.dashclock.phone.MissedCallsExtension;
import com.google.android.apps.dashclock.phone.SmsExtension;
import net.nurik.roman.dashclock.R;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import static com.google.android.apps.dashclock.LogUtils.LOGE;
/**
* Because every project needs a Utils class.
*/
public class Utils {
private static final String TAG = LogUtils.makeLogTag(Utils.class);
private static final String USER_AGENT = "DashClock/0.0";
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
public static final int EXTENSION_ICON_SIZE = 128;
private static final String[] CLOCK_PACKAGES = new String[] {
"com.google.android.deskclock",
"com.android.deskclock",
};
// TODO: Let's use a *real* HTTP library, eh?
public static HttpURLConnection openUrlConnection(String url) throws IOException {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.setChunkedStreamingMode(0);
conn.setRequestProperty("User-Agent", USER_AGENT);
conn.connect();
return conn;
}
private static final int BRIGHTNESS_THRESHOLD = 150;
public static boolean isColorDark(int color) {
return ((30 * Color.red(color) +
59 * Color.green(color) +
11 * Color.blue(color)) / 100) <= BRIGHTNESS_THRESHOLD;
}
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 (NoSuchFieldException ignored) {
} catch (IllegalAccessException ignored) {
} catch (ClassCastException ignored) {
} // TODO: catch all exceptions?
}
}
public static Bitmap flattenExtensionIcon(Drawable baseIcon, int color) {
if (baseIcon == null) {
return null;
}
Bitmap outBitmap = Bitmap.createBitmap(EXTENSION_ICON_SIZE, EXTENSION_ICON_SIZE,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(outBitmap);
baseIcon.setBounds(0, 0, EXTENSION_ICON_SIZE, EXTENSION_ICON_SIZE);
baseIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
baseIcon.draw(canvas);
baseIcon.setColorFilter(null);
baseIcon.setCallback(null); // free up any references
return outBitmap;
}
public static Bitmap flattenExtensionIcon(Context context, Bitmap baseIcon, int color) {
return flattenExtensionIcon(new BitmapDrawable(context.getResources(), baseIcon), color);
}
public static Bitmap loadExtensionIcon(Context context, ComponentName extension,
int icon, Uri iconUri, int color) {
if (iconUri != null) {
return loadExtensionIconFromUri(context, iconUri);
}
if (icon <= 0) {
return null;
}
String packageName = extension.getPackageName();
try {
Context packageContext = context.createPackageContext(packageName, 0);
Resources packageRes = packageContext.getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(packageRes, icon, options);
// Cut down the icon to a smaller size.
int sampleSize = 1;
while (true) {
if (options.outHeight / (sampleSize * 2) > Utils.EXTENSION_ICON_SIZE / 2) {
sampleSize *= 2;
} else {
break;
}
}
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
return Utils.flattenExtensionIcon(
context,
BitmapFactory.decodeResource(packageRes, icon, options),
color);
} catch (PackageManager.NameNotFoundException e) {
LOGE(TAG, "Couldn't access extension's package while loading icon data.");
}
return null;
}
public static Bitmap loadExtensionIconFromUri(Context context, Uri iconUri) {
try {
ParcelFileDescriptor pfd = context.getContentResolver()
.openFileDescriptor(iconUri, "r");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
// Cut down the icon to a smaller size.
int sampleSize = 1;
while (true) {
if (options.outHeight / (sampleSize * 2) > Utils.EXTENSION_ICON_SIZE / 2) {
sampleSize *= 2;
} else {
break;
}
}
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
return Utils.flattenExtensionIcon(
context,
BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options),
0xffffffff);
} catch (IOException e) {
LOGE(TAG, "Couldn't read icon from content URI.", e);
} catch (SecurityException e) {
LOGE(TAG, "Couldn't read icon from content URI.", e);
}
return null;
}
public static Intent getDefaultClockIntent(Context context) {
PackageManager pm = context.getPackageManager();
for (String packageName : CLOCK_PACKAGES) {
try {
pm.getPackageInfo(packageName, 0);
return pm.getLaunchIntentForPackage(packageName);
} catch (PackageManager.NameNotFoundException ignored) {
}
}
return null;
}
public static Intent getDefaultAlarmsIntent(Context context) {
// TODO: consider using AlarmClock.ACTION_SET_ALARM, although it requires a permission
PackageManager pm = context.getPackageManager();
for (String packageName : CLOCK_PACKAGES) {
try {
ComponentName cn = new ComponentName(packageName,
"com.android.deskclock.AlarmClock");
pm.getActivityInfo(cn, 0);
return Intent.makeMainActivity(cn);
} catch (PackageManager.NameNotFoundException ignored) {
}
}
return getDefaultClockIntent(context);
}
public static String expandedTitleOrStatus(ExtensionData data) {
String expandedTitle = data.expandedTitle();
if (TextUtils.isEmpty(expandedTitle)) {
expandedTitle = data.status();
if (!TextUtils.isEmpty(expandedTitle)) {
expandedTitle = expandedTitle.replace("\n", " ");
}
}
return expandedTitle;
}
private static Class[] sPhoneOnlyExtensions = {
SmsExtension.class,
MissedCallsExtension.class,
};
public static void enableDisablePhoneOnlyExtensions(Context context) {
PackageManager pm = context.getPackageManager();
boolean hasTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
for (Class ext : sPhoneOnlyExtensions) {
pm.setComponentEnabledSetting(new ComponentName(context, ext), hasTelephony
? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
}
}