2 * Copyright (C) 2012 Andrew Neal
3 * Copyright (C) 2014 The CyanogenMod Project
4 * Licensed under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with the
6 * License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
8 * or agreed to in writing, software distributed under the License is
9 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
10 * KIND, either express or implied. See the License for the specific language
11 * governing permissions and limitations under the License.
14 package com.cyanogenmod.eleven.utils;
16 import android.annotation.SuppressLint;
17 import android.app.Activity;
18 import android.content.Context;
19 import android.content.res.Configuration;
20 import android.database.Cursor;
21 import android.graphics.Color;
22 import android.graphics.Rect;
23 import android.net.ConnectivityManager;
24 import android.net.NetworkInfo;
25 import android.net.Uri;
26 import android.os.AsyncTask;
27 import android.os.Build;
28 import android.provider.BaseColumns;
29 import android.provider.MediaStore;
30 import android.util.Log;
31 import android.util.TypedValue;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
35 import android.widget.Toast;
37 import com.cyanogenmod.eleven.cache.ImageCache;
38 import com.cyanogenmod.eleven.cache.ImageFetcher;
40 import java.util.concurrent.RejectedExecutionHandler;
41 import java.util.concurrent.ThreadPoolExecutor;
44 * Mostly general and UI helpers.
46 * @author Andrew Neal (andrewdneal@gmail.com)
48 public final class ApolloUtils {
51 * The threshold used calculate if a color is light or dark
53 private static final int BRIGHTNESS_THRESHOLD = 130;
56 * Because cancelled tasks are not automatically removed from the queue, we can easily
57 * run over the queue limit - so here we will have a purge policy to purge those tasks
59 public static class PurgePolicy implements RejectedExecutionHandler {
60 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
61 // try purging all cancelled work items and re-executing
62 if (!e.isShutdown()) {
63 Log.d(PurgePolicy.class.getSimpleName(), "Before Purge: " + e.getQueue().size());
65 Log.d(PurgePolicy.class.getSimpleName(), "After Purge: " + e.getQueue().size());
72 ((ThreadPoolExecutor)AsyncTask.THREAD_POOL_EXECUTOR).setRejectedExecutionHandler(
77 /* This class is never initiated */
78 public ApolloUtils() {
82 * Used to determine if the device is a tablet or not
84 * @param context The {@link Context} to use.
85 * @return True if the device is a tablet, false otherwise.
87 public static final boolean isTablet(final Context context) {
88 final int layout = context.getResources().getConfiguration().screenLayout;
89 return (layout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
93 * Used to determine if the device is currently in landscape mode
95 * @param context The {@link Context} to use.
96 * @return True if the device is in landscape mode, false otherwise.
98 public static final boolean isLandscape(final Context context) {
99 final int orientation = context.getResources().getConfiguration().orientation;
100 return orientation == Configuration.ORIENTATION_LANDSCAPE;
104 * Execute an {@link AsyncTask} on a thread pool
106 * @param forceSerial True to force the task to run in serial order
107 * @param task Task to execute
108 * @param args Optional arguments to pass to
109 * {@link AsyncTask#execute(Object[])}
110 * @param <T> Task argument type
112 @SuppressLint("NewApi")
113 public static <T> void execute(final boolean forceSerial, final AsyncTask<T, ?, ?> task,
115 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.DONUT) {
116 throw new UnsupportedOperationException(
117 "This class can only be used on API 4 and newer.");
119 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || forceSerial) {
122 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, args);
127 * Used to determine if there is an active data connection and what type of
128 * connection it is if there is one
130 * @param context The {@link Context} to use
131 * @return True if there is an active data connection, false otherwise.
132 * Also, if the user has checked to only download via Wi-Fi in the
133 * settings, the mobile data and other network connections aren't
136 public static final boolean isOnline(final Context context) {
138 * This sort of handles a sudden configuration change, but I think it
139 * should be dealt with in a more professional way.
141 if (context == null) {
145 boolean state = false;
146 final boolean onlyOnWifi = PreferenceUtils.getInstance(context).onlyOnWifi();
148 /* Monitor network connections */
149 final ConnectivityManager connectivityManager = (ConnectivityManager)context
150 .getSystemService(Context.CONNECTIVITY_SERVICE);
152 /* Wi-Fi connection */
153 final NetworkInfo wifiNetwork = connectivityManager
154 .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
155 if (wifiNetwork != null) {
156 state = wifiNetwork.isConnectedOrConnecting();
159 /* Mobile data connection */
160 final NetworkInfo mbobileNetwork = connectivityManager
161 .getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
162 if (mbobileNetwork != null) {
164 state = mbobileNetwork.isConnectedOrConnecting();
169 final NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
170 if (activeNetwork != null) {
172 state = activeNetwork.isConnectedOrConnecting();
180 * Display a {@link Toast} letting the user know what an item does when long
183 * @param view The {@link View} to copy the content description from.
185 public static void showCheatSheet(final View view) {
187 final int[] screenPos = new int[2]; // origin is device display
188 final Rect displayFrame = new Rect(); // includes decorations (e.g.
190 view.getLocationOnScreen(screenPos);
191 view.getWindowVisibleDisplayFrame(displayFrame);
193 final Context context = view.getContext();
194 final int viewWidth = view.getWidth();
195 final int viewHeight = view.getHeight();
196 final int viewCenterX = screenPos[0] + viewWidth / 2;
197 final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
198 final int estimatedToastHeight = (int)(48 * context.getResources().getDisplayMetrics().density);
200 final Toast cheatSheet = Toast.makeText(context, view.getContentDescription(),
202 final boolean showBelow = screenPos[1] < estimatedToastHeight;
205 // Offsets are after decorations (e.g. status bar) are factored in
206 cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX
207 - screenWidth / 2, screenPos[1] - displayFrame.top + viewHeight);
210 // Offsets are after decorations (e.g. status bar) are factored in
211 cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, viewCenterX
212 - screenWidth / 2, displayFrame.bottom - screenPos[1]);
218 * Calculate whether a color is light or dark, based on a commonly known
219 * brightness formula.
221 * @see {@literal http://en.wikipedia.org/wiki/HSV_color_space%23Lightness}
223 public static final boolean isColorDark(final int color) {
224 return (30 * Color.red(color) + 59 * Color.green(color) + 11 * Color.blue(color)) / 100 <= BRIGHTNESS_THRESHOLD;
228 * Runs a piece of code after the next layout run
230 * @param view The {@link View} used.
231 * @param runnable The {@link Runnable} used after the next layout run
233 @SuppressLint("NewApi")
234 public static void doAfterLayout(final View view, final Runnable runnable) {
235 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
236 @SuppressWarnings("deprecation")
238 public void onGlobalLayout() {
239 /* Layout pass done, unregister for further events */
240 view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
244 view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
248 * Creates a new instance of the {@link ImageCache} and {@link ImageFetcher}
250 * @param activity The {@link Activity} to use.
251 * @return A new {@link ImageFetcher} used to fetch images asynchronously.
253 public static final ImageFetcher getImageFetcher(final Activity activity) {
254 final ImageFetcher imageFetcher = ImageFetcher.getInstance(activity);
255 imageFetcher.setImageCache(ImageCache.findOrCreateCache(activity));
260 * Method that removes the support for HardwareAcceleration from a {@link View}.<br/>
262 * Check AOSP notice:<br/>
264 * 'ComposeShader can only contain shaders of different types (a BitmapShader and a
265 * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your
266 * application is affected by any of these missing features or limitations, you can turn
267 * off hardware acceleration for just the affected portion of your application by calling
268 * setLayerType(View.LAYER_TYPE_SOFTWARE, null).'</pre>
272 public static void removeHardwareAccelerationSupport(View v) {
273 if (v.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
274 v.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
279 * Gets the action bar height in pixels
281 * @return action bar height in pixels
283 public static int getActionBarHeight(Context context) {
284 TypedValue tv = new TypedValue();
285 View view = new View(context);
286 if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
287 return TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics());
294 * Returns a fancy search query cursor
296 * @param query query string
297 * @return cursor of the results
299 public static Cursor createSearchQueryCursor(final Context context, final String query) {
300 final Uri uri = Uri.parse("content://media/external/audio/search/fancy/"
301 + Uri.encode(query));
302 final String[] projection = new String[] {
303 BaseColumns._ID, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Artists.ARTIST,
304 MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2"
307 // no selection/selection/sort args - they are ignored by fancy search anyways
308 return context.getContentResolver().query(uri, projection, null, null, null);
311 /** make a useful message from an exception without the stack track */
312 public static String formatException(String message, Exception e) {
313 StringBuilder builder = new StringBuilder();
314 if(message != null) {
315 builder.append(message);
316 if(e != null) { builder.append(" - "); }
320 builder.append(e.getClass().getSimpleName());
322 String exceptionMessage = e.getMessage();
323 if(exceptionMessage != null) {
324 builder.append(": ");
325 builder.append(exceptionMessage);
328 for(Throwable cause = e.getCause(); cause != null; cause = cause.getCause()) {
329 builder.append(" (cause ");
330 builder.append(cause.getClass().getSimpleName());
331 String causeMessage = e.getMessage();
332 if(causeMessage != null) {
333 builder.append(": ");
334 builder.append(exceptionMessage);
340 return builder.toString();