OSDN Git Service

Eleven: Cleanup all the whitespace
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / utils / ApolloUtils.java
1 /*
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.
12  */
13
14 package com.cyanogenmod.eleven.utils;
15
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;
36
37 import com.cyanogenmod.eleven.cache.ImageCache;
38 import com.cyanogenmod.eleven.cache.ImageFetcher;
39
40 import java.util.concurrent.RejectedExecutionHandler;
41 import java.util.concurrent.ThreadPoolExecutor;
42
43 /**
44  * Mostly general and UI helpers.
45  *
46  * @author Andrew Neal (andrewdneal@gmail.com)
47  */
48 public final class ApolloUtils {
49
50     /**
51      * The threshold used calculate if a color is light or dark
52      */
53     private static final int BRIGHTNESS_THRESHOLD = 130;
54
55     /**
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
58      */
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());
64                 e.purge();
65                 Log.d(PurgePolicy.class.getSimpleName(), "After Purge: " + e.getQueue().size());
66                 e.execute(r);
67             }
68         }
69     };
70
71     static {
72         ((ThreadPoolExecutor)AsyncTask.THREAD_POOL_EXECUTOR).setRejectedExecutionHandler(
73                 new PurgePolicy()
74         );
75     }
76
77     /* This class is never initiated */
78     public ApolloUtils() {
79     }
80
81     /**
82      * Used to determine if the device is a tablet or not
83      *
84      * @param context The {@link Context} to use.
85      * @return True if the device is a tablet, false otherwise.
86      */
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;
90     }
91
92     /**
93      * Used to determine if the device is currently in landscape mode
94      *
95      * @param context The {@link Context} to use.
96      * @return True if the device is in landscape mode, false otherwise.
97      */
98     public static final boolean isLandscape(final Context context) {
99         final int orientation = context.getResources().getConfiguration().orientation;
100         return orientation == Configuration.ORIENTATION_LANDSCAPE;
101     }
102
103     /**
104      * Execute an {@link AsyncTask} on a thread pool
105      *
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
111      */
112     @SuppressLint("NewApi")
113     public static <T> void execute(final boolean forceSerial, final AsyncTask<T, ?, ?> task,
114             final T... args) {
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.");
118         }
119         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || forceSerial) {
120             task.execute(args);
121         } else {
122             task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, args);
123         }
124     }
125
126     /**
127      * Used to determine if there is an active data connection and what type of
128      * connection it is if there is one
129      *
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
134      *         returned at all
135      */
136     public static final boolean isOnline(final Context context) {
137         /*
138          * This sort of handles a sudden configuration change, but I think it
139          * should be dealt with in a more professional way.
140          */
141         if (context == null) {
142             return false;
143         }
144
145         boolean state = false;
146         final boolean onlyOnWifi = PreferenceUtils.getInstance(context).onlyOnWifi();
147
148         /* Monitor network connections */
149         final ConnectivityManager connectivityManager = (ConnectivityManager)context
150                 .getSystemService(Context.CONNECTIVITY_SERVICE);
151
152         /* Wi-Fi connection */
153         final NetworkInfo wifiNetwork = connectivityManager
154                 .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
155         if (wifiNetwork != null) {
156             state = wifiNetwork.isConnectedOrConnecting();
157         }
158
159         /* Mobile data connection */
160         final NetworkInfo mbobileNetwork = connectivityManager
161                 .getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
162         if (mbobileNetwork != null) {
163             if (!onlyOnWifi) {
164                 state = mbobileNetwork.isConnectedOrConnecting();
165             }
166         }
167
168         /* Other networks */
169         final NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
170         if (activeNetwork != null) {
171             if (!onlyOnWifi) {
172                 state = activeNetwork.isConnectedOrConnecting();
173             }
174         }
175
176         return state;
177     }
178
179     /**
180      * Display a {@link Toast} letting the user know what an item does when long
181      * pressed.
182      *
183      * @param view The {@link View} to copy the content description from.
184      */
185     public static void showCheatSheet(final View view) {
186
187         final int[] screenPos = new int[2]; // origin is device display
188         final Rect displayFrame = new Rect(); // includes decorations (e.g.
189                                               // status bar)
190         view.getLocationOnScreen(screenPos);
191         view.getWindowVisibleDisplayFrame(displayFrame);
192
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);
199
200         final Toast cheatSheet = Toast.makeText(context, view.getContentDescription(),
201                 Toast.LENGTH_SHORT);
202         final boolean showBelow = screenPos[1] < estimatedToastHeight;
203         if (showBelow) {
204             // Show below
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);
208         } else {
209             // Show above
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]);
213         }
214         cheatSheet.show();
215     }
216
217     /**
218      * Calculate whether a color is light or dark, based on a commonly known
219      * brightness formula.
220      *
221      * @see {@literal http://en.wikipedia.org/wiki/HSV_color_space%23Lightness}
222      */
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;
225     }
226
227     /**
228      * Runs a piece of code after the next layout run
229      *
230      * @param view The {@link View} used.
231      * @param runnable The {@link Runnable} used after the next layout run
232      */
233     @SuppressLint("NewApi")
234     public static void doAfterLayout(final View view, final Runnable runnable) {
235         final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
236             @SuppressWarnings("deprecation")
237             @Override
238             public void onGlobalLayout() {
239                 /* Layout pass done, unregister for further events */
240                 view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
241                 runnable.run();
242             }
243         };
244         view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
245     }
246
247     /**
248      * Creates a new instance of the {@link ImageCache} and {@link ImageFetcher}
249      *
250      * @param activity The {@link Activity} to use.
251      * @return A new {@link ImageFetcher} used to fetch images asynchronously.
252      */
253     public static final ImageFetcher getImageFetcher(final Activity activity) {
254         final ImageFetcher imageFetcher = ImageFetcher.getInstance(activity);
255         imageFetcher.setImageCache(ImageCache.findOrCreateCache(activity));
256         return imageFetcher;
257     }
258
259     /**
260      * Method that removes the support for HardwareAcceleration from a {@link View}.<br/>
261      * <br/>
262      * Check AOSP notice:<br/>
263      * <pre>
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>
269      *
270      * @param v The view
271      */
272     public static void removeHardwareAccelerationSupport(View v) {
273         if (v.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
274             v.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
275         }
276    }
277
278     /**
279      * Gets the action bar height in pixels
280      * @param context
281      * @return action bar height in pixels
282      */
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());
288         }
289
290         return 0;
291     }
292
293     /**
294      * Returns a fancy search query cursor
295      * @param context
296      * @param query query string
297      * @return cursor of the results
298      */
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"
305         };
306
307         // no selection/selection/sort args - they are ignored by fancy search anyways
308         return context.getContentResolver().query(uri, projection, null, null, null);
309     }
310
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(" - "); }
317         }
318
319         if(e != null) {
320             builder.append(e.getClass().getSimpleName());
321
322             String exceptionMessage = e.getMessage();
323             if(exceptionMessage != null) {
324                 builder.append(": ");
325                 builder.append(exceptionMessage);
326             }
327
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);
335                 }
336                 builder.append(")");
337             }
338         }
339
340         return builder.toString();
341     }
342 }