2 * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3 * (the "License"); you may not use this file except in compliance with the
4 * License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
12 package com.andrew.apollo.utils;
14 import android.annotation.SuppressLint;
15 import android.app.Activity;
16 import android.app.AlertDialog;
17 import android.content.Context;
18 import android.content.DialogInterface;
19 import android.content.DialogInterface.OnClickListener;
20 import android.content.Intent;
21 import android.content.res.Configuration;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.Color;
25 import android.graphics.Rect;
26 import android.net.ConnectivityManager;
27 import android.net.NetworkInfo;
28 import android.os.AsyncTask;
29 import android.os.Build;
30 import android.provider.MediaStore;
31 import android.util.Log;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
35 import android.webkit.WebView;
36 import android.widget.Toast;
38 import com.andrew.apollo.Config;
39 import com.andrew.apollo.R;
40 import com.andrew.apollo.cache.ImageCache;
41 import com.andrew.apollo.cache.ImageFetcher;
42 import com.andrew.apollo.ui.activities.ShortcutActivity;
43 import com.andrew.apollo.widgets.ColorPickerView;
44 import com.andrew.apollo.widgets.ColorSchemeDialog;
45 import com.devspark.appmsg.AppMsg;
48 * Mostly general and UI helpers.
50 * @author Andrew Neal (andrewdneal@gmail.com)
52 public final class ApolloUtils {
55 * The threshold used calculate if a color is light or dark
57 private static final int BRIGHTNESS_THRESHOLD = 130;
59 /* This class is never initiated */
60 public ApolloUtils() {
64 * Used to determine if the device is running Jelly Bean or greater
66 * @return True if the device is running Jelly Bean or greater, false
69 public static final boolean hasJellyBean() {
70 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
74 * Used to determine if the device is running
75 * Jelly Bean MR2 (Android 4.3) or greater
77 * @return True if the device is running Jelly Bean MR2 or greater,
80 public static final boolean hasJellyBeanMR2() {
81 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
85 * Used to determine if the device is a tablet or not
87 * @param context The {@link Context} to use.
88 * @return True if the device is a tablet, false otherwise.
90 public static final boolean isTablet(final Context context) {
91 final int layout = context.getResources().getConfiguration().screenLayout;
92 return (layout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
96 * Used to determine if the device is currently in landscape mode
98 * @param context The {@link Context} to use.
99 * @return True if the device is in landscape mode, false otherwise.
101 public static final boolean isLandscape(final Context context) {
102 final int orientation = context.getResources().getConfiguration().orientation;
103 return orientation == Configuration.ORIENTATION_LANDSCAPE;
107 * Execute an {@link AsyncTask} on a thread pool
109 * @param forceSerial True to force the task to run in serial order
110 * @param task Task to execute
111 * @param args Optional arguments to pass to
112 * {@link AsyncTask#execute(Object[])}
113 * @param <T> Task argument type
115 @SuppressLint("NewApi")
116 public static <T> void execute(final boolean forceSerial, final AsyncTask<T, ?, ?> task,
118 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.DONUT) {
119 throw new UnsupportedOperationException(
120 "This class can only be used on API 4 and newer.");
122 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || forceSerial) {
125 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, args);
130 * Used to determine if there is an active data connection and what type of
131 * connection it is if there is one
133 * @param context The {@link Context} to use
134 * @return True if there is an active data connection, false otherwise.
135 * Also, if the user has checked to only download via Wi-Fi in the
136 * settings, the mobile data and other network connections aren't
139 public static final boolean isOnline(final Context context) {
141 * This sort of handles a sudden configuration change, but I think it
142 * should be dealt with in a more professional way.
144 if (context == null) {
148 boolean state = false;
149 final boolean onlyOnWifi = PreferenceUtils.getInstance(context).onlyOnWifi();
151 /* Monitor network connections */
152 final ConnectivityManager connectivityManager = (ConnectivityManager)context
153 .getSystemService(Context.CONNECTIVITY_SERVICE);
155 /* Wi-Fi connection */
156 final NetworkInfo wifiNetwork = connectivityManager
157 .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
158 if (wifiNetwork != null) {
159 state = wifiNetwork.isConnectedOrConnecting();
162 /* Mobile data connection */
163 final NetworkInfo mbobileNetwork = connectivityManager
164 .getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
165 if (mbobileNetwork != null) {
167 state = mbobileNetwork.isConnectedOrConnecting();
172 final NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
173 if (activeNetwork != null) {
175 state = activeNetwork.isConnectedOrConnecting();
183 * Display a {@link Toast} letting the user know what an item does when long
186 * @param view The {@link View} to copy the content description from.
188 public static void showCheatSheet(final View view) {
190 final int[] screenPos = new int[2]; // origin is device display
191 final Rect displayFrame = new Rect(); // includes decorations (e.g.
193 view.getLocationOnScreen(screenPos);
194 view.getWindowVisibleDisplayFrame(displayFrame);
196 final Context context = view.getContext();
197 final int viewWidth = view.getWidth();
198 final int viewHeight = view.getHeight();
199 final int viewCenterX = screenPos[0] + viewWidth / 2;
200 final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
201 final int estimatedToastHeight = (int)(48 * context.getResources().getDisplayMetrics().density);
203 final Toast cheatSheet = Toast.makeText(context, view.getContentDescription(),
205 final boolean showBelow = screenPos[1] < estimatedToastHeight;
208 // Offsets are after decorations (e.g. status bar) are factored in
209 cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX
210 - screenWidth / 2, screenPos[1] - displayFrame.top + viewHeight);
213 // Offsets are after decorations (e.g. status bar) are factored in
214 cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, viewCenterX
215 - screenWidth / 2, displayFrame.bottom - screenPos[1]);
221 * @param context The {@link Context} to use.
222 * @return An {@link AlertDialog} used to show the open source licenses used
225 public static final AlertDialog createOpenSourceDialog(final Context context) {
226 final WebView webView = new WebView(context);
227 webView.loadUrl("file:///android_asset/licenses.html");
228 return new AlertDialog.Builder(context)
229 .setTitle(R.string.settings_open_source_licenses)
231 .setPositiveButton(android.R.string.ok, null)
236 * Calculate whether a color is light or dark, based on a commonly known
237 * brightness formula.
239 * @see {@literal http://en.wikipedia.org/wiki/HSV_color_space%23Lightness}
241 public static final boolean isColorDark(final int color) {
242 return (30 * Color.red(color) + 59 * Color.green(color) + 11 * Color.blue(color)) / 100 <= BRIGHTNESS_THRESHOLD;
246 * Runs a piece of code after the next layout run
248 * @param view The {@link View} used.
249 * @param runnable The {@link Runnable} used after the next layout run
251 @SuppressLint("NewApi")
252 public static void doAfterLayout(final View view, final Runnable runnable) {
253 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
254 @SuppressWarnings("deprecation")
256 public void onGlobalLayout() {
257 /* Layout pass done, unregister for further events */
258 if (hasJellyBean()) {
259 view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
261 view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
266 view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
270 * Creates a new instance of the {@link ImageCache} and {@link ImageFetcher}
272 * @param activity The {@link Activity} to use.
273 * @return A new {@link ImageFetcher} used to fetch images asynchronously.
275 public static final ImageFetcher getImageFetcher(final Activity activity) {
276 final ImageFetcher imageFetcher = ImageFetcher.getInstance(activity);
277 imageFetcher.setImageCache(ImageCache.findOrCreateCache(activity));
282 * Used to create shortcuts for an artist, album, or playlist that is then
283 * placed on the default launcher homescreen
285 * @param displayName The shortcut name
286 * @param id The ID of the artist, album, playlist, or genre
287 * @param mimeType The MIME type of the shortcut
288 * @param context The {@link Context} to use to
290 public static void createShortcutIntent(final String displayName, final String artistName,
291 final Long id, final String mimeType, final Activity context) {
293 final ImageFetcher fetcher = getImageFetcher(context);
294 Bitmap bitmap = null;
295 if (mimeType.equals(MediaStore.Audio.Albums.CONTENT_TYPE)) {
296 bitmap = fetcher.getCachedBitmap(
297 ImageFetcher.generateAlbumCacheKey(displayName, artistName));
299 bitmap = fetcher.getCachedBitmap(displayName);
301 if (bitmap == null) {
302 bitmap = BitmapFactory.decodeResource(context.getResources(),
303 R.drawable.default_artwork);
306 // Intent used when the icon is touched
307 final Intent shortcutIntent = new Intent(context, ShortcutActivity.class);
308 shortcutIntent.setAction(Intent.ACTION_VIEW);
309 shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
310 shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
311 shortcutIntent.putExtra(Config.ID, id);
312 shortcutIntent.putExtra(Config.NAME, displayName);
313 shortcutIntent.putExtra(Config.MIME_TYPE, mimeType);
315 // Intent that actually sets the shortcut
316 final Intent intent = new Intent();
317 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, BitmapUtils.resizeAndCropCenter(bitmap, 96));
318 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
319 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
320 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
321 context.sendBroadcast(intent);
322 AppMsg.makeText(context,
323 context.getString(R.string.pinned_to_home_screen, displayName),
324 AppMsg.STYLE_CONFIRM).show();
325 } catch (final Exception e) {
326 Log.e("ApolloUtils", "createShortcutIntent", e);
329 context.getString(R.string.could_not_be_pinned_to_home_screen, displayName),
330 AppMsg.STYLE_ALERT).show();
335 * Shows the {@link ColorPickerView}
337 * @param context The {@link Context} to use.
339 public static void showColorPicker(final Context context) {
340 final ColorSchemeDialog colorPickerView = new ColorSchemeDialog(context);
341 colorPickerView.setButton(AlertDialog.BUTTON_POSITIVE,
342 context.getString(android.R.string.ok), new OnClickListener() {
345 public void onClick(final DialogInterface dialog, final int which) {
346 PreferenceUtils.getInstance(context).setDefaultThemeColor(
347 colorPickerView.getColor());
350 colorPickerView.setButton(AlertDialog.BUTTON_NEGATIVE,
351 context.getString(R.string.cancel), (OnClickListener) null);
352 colorPickerView.show();
356 * Method that removes the support for HardwareAcceleration from a {@link View}.<br/>
358 * Check AOSP notice:<br/>
360 * 'ComposeShader can only contain shaders of different types (a BitmapShader and a
361 * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your
362 * application is affected by any of these missing features or limitations, you can turn
363 * off hardware acceleration for just the affected portion of your application by calling
364 * setLayerType(View.LAYER_TYPE_SOFTWARE, null).'</pre>
368 public static void removeHardwareAccelerationSupport(View v) {
369 if (v.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
370 v.setLayerType(View.LAYER_TYPE_SOFTWARE, null);