Due to the limitation of {@link AccountManager}, we can only access a value by its key, but we
28 | * cannot access the key set or the entire map. So {@link #getAll()} and {@link #clear()} are
29 | * unsupported, {@link #edit()}, {@link #commit()} and {@link #apply()} are stub, and changes are
30 | * committed immediately.
31 | *
32 | *
Also due to the limitation of {@link AccountManager}, all values are stored as {@link String},
33 | * so {@link #putStringSet(String, Set)} needs to flatten the {@link String} array into one
34 | * {@link String}, in this case the character bar ('|') is used as the delimiter.
Due to the limitation of {@link AccountManager}, all values are stored as {@link String},
227 | * so {@link #putStringSet(String, Set)} needs to flatten the {@link String} array into one
228 | * {@link String}, in this case the character bar ('|') is used as the delimiter.
229 | */
230 | @Override
231 | public AccountPreferences putStringSet(String key, Set value) {
232 | return putString(key, value != null ? IoUtils.collectionToString(value) : null);
233 | }
234 |
235 | /**
236 | * {@inheritDoc}
237 | */
238 | @Override
239 | public AccountPreferences putInt(String key, int value) {
240 | return putString(key, Integer.toString(value));
241 | }
242 |
243 | /**
244 | * {@inheritDoc}
245 | */
246 | @Override
247 | public AccountPreferences putLong(String key, long value) {
248 | return putString(key, Long.toString(value));
249 | }
250 |
251 | /**
252 | * {@inheritDoc}
253 | */
254 | @Override
255 | public AccountPreferences putFloat(String key, float value) {
256 | return putString(key, Float.toString(value));
257 | }
258 |
259 | /**
260 | * {@inheritDoc}
261 | */
262 | @Override
263 | public AccountPreferences putBoolean(String key, boolean value) {
264 | return putString(key, value ? TRUE_STRING : FALSE_STRING);
265 | }
266 |
267 | /**
268 | * {@inheritDoc}
269 | */
270 | @Override
271 | public AccountPreferences remove(String key) {
272 | return putString(key, null);
273 | }
274 |
275 | /**
276 | * Unsupported operation.
277 | *
278 | * Due to the limitation of {@link AccountManager}, we can only access a value by its key, but we
279 | * cannot access the key set or the entire map. Calling this method will throw an
280 | * {@link UnsupportedOperationException}.
281 | *
282 | * @throws UnsupportedOperationException
283 | */
284 | @Override
285 | public Editor clear() {
286 | throw new UnsupportedOperationException("clear() is not supported by AccountManager");
287 | }
288 |
289 | /**
290 | * Stub method.
291 | *
292 | * Due to the limitation of {@link AccountManager}, we cannot batch commit changes, so nothing
293 | * is done and it will always success.
294 | *
295 | * @return Always returns true.
296 | */
297 | @Override
298 | public boolean commit() {
299 | return true;
300 | }
301 |
302 | /**
303 | * Stub method.
304 | *
305 | * Due to the limitation of {@link AccountManager}, we cannot batch commit changes, so nothing
306 | * is done.
307 | */
308 | @Override
309 | public void apply() {}
310 |
311 | /**
312 | * {@inheritDoc}
313 | */
314 | @Override
315 | public void registerOnSharedPreferenceChangeListener(
316 | OnSharedPreferenceChangeListener listener) {
317 | listeners.add(listener);
318 | }
319 |
320 | /**
321 | * {@inheritDoc}
322 | */
323 | @Override
324 | public void unregisterOnSharedPreferenceChangeListener(
325 | OnSharedPreferenceChangeListener listener) {
326 | listeners.remove(listener);
327 | }
328 |
329 | private void notifyChanged(final String key) {
330 | if (Looper.myLooper() == Looper.getMainLooper()) {
331 | for (OnSharedPreferenceChangeListener listener : listeners) {
332 | if (listener != null) {
333 | listener.onSharedPreferenceChanged(AccountPreferences.this, key);
334 | }
335 | }
336 | } else {
337 | mainHandler.post(new Runnable() {
338 | @Override
339 | public void run() {
340 | notifyChanged(key);
341 | }
342 | });
343 | }
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/library/src/main/java/me/zhanghai/android/androidutil/util/PRNGFixes.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 Zhang Hai
3 | * All Rights Reserved.
4 | */
5 |
6 | package me.zhanghai.android.androidutil.util;
7 |
8 | // NOTE: The code below is copied from
9 | // http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
10 |
11 | /*
12 | * This software is provided 'as-is', without any express or implied
13 | * warranty. In no event will Google be held liable for any damages
14 | * arising from the use of this software.
15 | *
16 | * Permission is granted to anyone to use this software for any purpose,
17 | * including commercial applications, and to alter it and redistribute it
18 | * freely, as long as the origin is not misrepresented.
19 | */
20 |
21 | import android.os.Build;
22 | import android.os.Process;
23 | import android.util.Log;
24 |
25 | import java.io.ByteArrayOutputStream;
26 | import java.io.DataInputStream;
27 | import java.io.DataOutputStream;
28 | import java.io.File;
29 | import java.io.FileInputStream;
30 | import java.io.FileOutputStream;
31 | import java.io.IOException;
32 | import java.io.OutputStream;
33 | import java.io.UnsupportedEncodingException;
34 | import java.security.NoSuchAlgorithmException;
35 | import java.security.Provider;
36 | import java.security.SecureRandom;
37 | import java.security.SecureRandomSpi;
38 | import java.security.Security;
39 |
40 | /**
41 | * Fixes for the output of the default PRNG having low entropy.
42 | *
43 | *
The fixes need to be applied via {@link #apply()} before any use of Java Cryptography
44 | * Architecture primitives. A good place to invoke them is in the
45 | * {@link android.app.Application#onCreate}.
46 | */
47 | public final class PRNGFixes {
48 |
49 | private static final int VERSION_CODE_JELLY_BEAN = 16;
50 | private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
51 | private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
52 | getBuildFingerprintAndDeviceSerial();
53 |
54 | private PRNGFixes() {}
55 |
56 | /**
57 | * Applies all fixes.
58 | *
59 | * @throws SecurityException if a fix is needed but could not be applied.
60 | */
61 | public static void apply() {
62 | applyOpenSSLFix();
63 | installLinuxPRNGSecureRandom();
64 | }
65 |
66 | /**
67 | * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the fix is not needed.
68 | *
69 | * @throws SecurityException if the fix is needed but could not be applied.
70 | */
71 | private static void applyOpenSSLFix() throws SecurityException {
72 |
73 | if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
74 | || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
75 | // No need to apply the fix
76 | return;
77 | }
78 |
79 | try {
80 | // Mix in the device- and invocation-specific seed.
81 | Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
82 | .getMethod("RAND_seed", byte[].class)
83 | .invoke(null, generateSeed());
84 |
85 | // Mix output of Linux PRNG into OpenSSL's PRNG
86 | int bytesRead = (Integer) Class.forName(
87 | "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
88 | .getMethod("RAND_load_file", String.class, long.class)
89 | .invoke(null, "/dev/urandom", 1024);
90 | if (bytesRead != 1024) {
91 | throw new IOException(
92 | "Unexpected number of bytes read from Linux PRNG: " + bytesRead);
93 | }
94 | } catch (Exception e) {
95 | throw new SecurityException("Failed to seed OpenSSL PRNG", e);
96 | }
97 | }
98 |
99 | /**
100 | * Installs a Linux PRNG-backed {@link SecureRandom} implementation as the default. Does nothing
101 | * if the implementation is already the default or if there is not need to install the
102 | * implementation.
103 | *
104 | * @throws SecurityException if the fix is needed but could not be applied.
105 | */
106 | private static void installLinuxPRNGSecureRandom()
107 | throws SecurityException {
108 |
109 | if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
110 | // No need to apply the fix
111 | return;
112 | }
113 |
114 | // Install a Linux PRNG-based SecureRandom implementation as the
115 | // default, if not yet installed.
116 | Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
117 | if ((secureRandomProviders == null)
118 | || (secureRandomProviders.length < 1)
119 | || (!LinuxPRNGSecureRandomProvider.class.equals(
120 | secureRandomProviders[0].getClass()))) {
121 | Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
122 | }
123 |
124 | // Assert that new SecureRandom() and
125 | // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
126 | // by the Linux PRNG-based SecureRandom implementation.
127 | SecureRandom rng1 = new SecureRandom();
128 | if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) {
129 | throw new SecurityException("new SecureRandom() backed by wrong Provider: "
130 | + rng1.getProvider().getClass());
131 | }
132 |
133 | SecureRandom rng2;
134 | try {
135 | rng2 = SecureRandom.getInstance("SHA1PRNG");
136 | } catch (NoSuchAlgorithmException e) {
137 | throw new SecurityException("SHA1PRNG not available", e);
138 | }
139 | if (!LinuxPRNGSecureRandomProvider.class.equals(
140 | rng2.getProvider().getClass())) {
141 | throw new SecurityException(
142 | "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong Provider: "
143 | + rng2.getProvider().getClass());
144 | }
145 | }
146 |
147 | /**
148 | * {@code Provider} of {@link SecureRandom} engines which pass through all requests to the Linux
149 | * PRNG.
150 | */
151 | private static class LinuxPRNGSecureRandomProvider extends Provider {
152 |
153 | public LinuxPRNGSecureRandomProvider() {
154 | super("LinuxPRNG", 1.0,
155 | "A Linux-specific random number provider that uses /dev/urandom");
156 | // Although /dev/urandom is not a SHA-1 PRNG, some apps explicitly request a SHA1PRNG
157 | // SecureRandom and we thus need to prevent them from getting the default implementation
158 | // whose output may have low entropy.
159 | put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
160 | put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
161 | }
162 | }
163 |
164 | /**
165 | * {@link SecureRandomSpi} which passes all requests to the Linux PRNG ({@code /dev/urandom}).
166 | */
167 | public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
168 |
169 | /*
170 | * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed are passed through
171 | * to the Linux PRNG (/dev/urandom). Instances of this class seed themselves by mixing in
172 | * the current time, PID, UID, build fingerprint, and hardware serial number (where
173 | * available) into Linux PRNG.
174 | *
175 | * Concurrency: Read requests to the underlying Linux PRNG are serialized (on sLock) to
176 | * ensure that multiple threads do not get duplicated PRNG output.
177 | */
178 |
179 | private static final File URANDOM_FILE = new File("/dev/urandom");
180 |
181 | private static final Object sLock = new Object();
182 |
183 | /**
184 | * Input stream for reading from Linux PRNG or {@code null} if not yet opened.
185 | *
186 | * @GuardedBy("sLock")
187 | */
188 | private static DataInputStream sUrandomIn;
189 |
190 | /**
191 | * Output stream for writing to Linux PRNG or {@code null} if not yet opened.
192 | *
193 | * @GuardedBy("sLock")
194 | */
195 | private static OutputStream sUrandomOut;
196 |
197 | /**
198 | * Whether this engine instance has been seeded. This is needed because each instance needs
199 | * to seed itself if the client does not explicitly seed it.
200 | */
201 | private boolean mSeeded;
202 |
203 | @Override
204 | protected void engineSetSeed(byte[] bytes) {
205 | try {
206 | OutputStream out;
207 | synchronized (sLock) {
208 | out = getUrandomOutputStream();
209 | }
210 | out.write(bytes);
211 | out.flush();
212 | } catch (IOException e) {
213 | // On a small fraction of devices /dev/urandom is not writable. Log and ignore.
214 | Log.w(PRNGFixes.class.getSimpleName(), "Failed to mix seed into " + URANDOM_FILE);
215 | } finally {
216 | mSeeded = true;
217 | }
218 | }
219 |
220 | @Override
221 | protected void engineNextBytes(byte[] bytes) {
222 |
223 | if (!mSeeded) {
224 | // Mix in the device- and invocation-specific seed.
225 | engineSetSeed(generateSeed());
226 | }
227 |
228 | try {
229 | DataInputStream in;
230 | synchronized (sLock) {
231 | in = getUrandomInputStream();
232 | }
233 | synchronized (in) {
234 | in.readFully(bytes);
235 | }
236 | } catch (IOException e) {
237 | throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
238 | }
239 | }
240 |
241 | @Override
242 | protected byte[] engineGenerateSeed(int size) {
243 | byte[] seed = new byte[size];
244 | engineNextBytes(seed);
245 | return seed;
246 | }
247 |
248 | private DataInputStream getUrandomInputStream() {
249 | synchronized (sLock) {
250 | if (sUrandomIn == null) {
251 | // NOTE: Consider inserting a BufferedInputStream between DataInputStream and
252 | // FileInputStream if you need higher PRNG output performance and can live with
253 | // future PRNG output being pulled into this process prematurely.
254 | try {
255 | sUrandomIn = new DataInputStream(
256 | new FileInputStream(URANDOM_FILE));
257 | } catch (IOException e) {
258 | throw new SecurityException("Failed to open "
259 | + URANDOM_FILE + " for reading", e);
260 | }
261 | }
262 | return sUrandomIn;
263 | }
264 | }
265 |
266 | private OutputStream getUrandomOutputStream() throws IOException {
267 | synchronized (sLock) {
268 | if (sUrandomOut == null) {
269 | sUrandomOut = new FileOutputStream(URANDOM_FILE);
270 | }
271 | return sUrandomOut;
272 | }
273 | }
274 | }
275 |
276 | /**
277 | * Generates a device- and invocation-specific seed to be mixed into the Linux PRNG.
278 | */
279 | private static byte[] generateSeed() {
280 | try {
281 | ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
282 | DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
283 | seedBufferOut.writeLong(System.currentTimeMillis());
284 | seedBufferOut.writeLong(System.nanoTime());
285 | seedBufferOut.writeInt(Process.myPid());
286 | seedBufferOut.writeInt(Process.myUid());
287 | seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
288 | seedBufferOut.close();
289 | return seedBuffer.toByteArray();
290 | } catch (IOException e) {
291 | throw new SecurityException("Failed to generate seed", e);
292 | }
293 | }
294 |
295 | /**
296 | * Gets the hardware serial number of this device.
297 | *
298 | * @return serial number or {@code null} if not available.
299 | */
300 | private static String getDeviceSerialNumber() {
301 | // We're using the Reflection API because Build.SERIAL is only available since API Level 9
302 | // (Gingerbread, Android 2.3).
303 | try {
304 | return (String) Build.class.getField("SERIAL").get(null);
305 | } catch (Exception ignored) {
306 | return null;
307 | }
308 | }
309 |
310 | private static byte[] getBuildFingerprintAndDeviceSerial() {
311 | StringBuilder result = new StringBuilder();
312 | String fingerprint = Build.FINGERPRINT;
313 | if (fingerprint != null) {
314 | result.append(fingerprint);
315 | }
316 | String serial = getDeviceSerialNumber();
317 | if (serial != null) {
318 | result.append(serial);
319 | }
320 | try {
321 | return result.toString().getBytes("UTF-8");
322 | } catch (UnsupportedEncodingException e) {
323 | throw new RuntimeException("UTF-8 encoding not supported");
324 | }
325 | }
326 | }
327 |
--------------------------------------------------------------------------------
/library/src/main/java/me/zhanghai/android/androidutil/util/AppUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014 Zhang Hai
3 | * All Rights Reserved.
4 | */
5 |
6 | package me.zhanghai.android.androidutil.util;
7 |
8 | import android.animation.Animator;
9 | import android.animation.AnimatorListenerAdapter;
10 | import android.annotation.SuppressLint;
11 | import android.annotation.TargetApi;
12 | import android.app.ActionBar;
13 | import android.app.Activity;
14 | import android.app.AlarmManager;
15 | import android.app.Fragment;
16 | import android.app.FragmentManager;
17 | import android.app.PendingIntent;
18 | import android.content.ActivityNotFoundException;
19 | import android.content.ComponentName;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.content.pm.PackageInfo;
23 | import android.content.pm.PackageManager;
24 | import android.content.res.TypedArray;
25 | import android.graphics.drawable.Drawable;
26 | import android.os.Build;
27 | import android.os.Bundle;
28 | import android.os.Handler;
29 | import android.support.v4.app.NavUtils;
30 | import android.support.v4.app.TaskStackBuilder;
31 | import android.transition.Fade;
32 | import android.transition.TransitionManager;
33 | import android.transition.TransitionSet;
34 | import android.util.SparseBooleanArray;
35 | import android.util.TypedValue;
36 | import android.view.LayoutInflater;
37 | import android.view.View;
38 | import android.view.ViewGroup;
39 | import android.view.ViewTreeObserver;
40 | import android.view.Window;
41 | import android.webkit.CookieManager;
42 | import android.widget.AbsListView;
43 | import android.widget.LinearLayout;
44 | import android.widget.Spinner;
45 |
46 | import com.myqsc.mobile3.main.info.DrawerManager;
47 | import com.myqsc.mobile3.main.ui.MainActivity;
48 | import com.myqsc.mobile3.ui.RestartApplicationActivity;
49 |
50 | import java.util.ArrayList;
51 | import java.util.List;
52 |
53 | public class AppUtils {
54 |
55 | public enum ActivityTransitionType {
56 | ACTIVITY_OPEN,
57 | ACTIVITY_CLOSE,
58 | SLIDE_IN_UP,
59 | SLIDE_OUT_DOWN,
60 | FADE
61 | }
62 |
63 | public static final int ACTIONBAR_DISPLAY_OPTIONS = ActionBar.DISPLAY_SHOW_HOME
64 | | ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE;
65 |
66 | public static final int ACTIONBAR_DISPLAY_OPTIONS_NO_UP = ACTIONBAR_DISPLAY_OPTIONS
67 | & ~ActionBar.DISPLAY_HOME_AS_UP;
68 |
69 | private static final int RESTART_APPLICATION_DELAY = 250;
70 |
71 | public static void crossfadeViews(final View fromView, View toView) {
72 |
73 | int duration = fromView.getResources()
74 | .getInteger(android.R.integer.config_shortAnimTime);
75 |
76 | fromView.animate()
77 | .setDuration(duration)
78 | .alpha(0)
79 | .setListener(new AnimatorListenerAdapter() {
80 | private boolean mCanceled = false;
81 | @Override
82 | public void onAnimationCancel(Animator animation) {
83 | mCanceled = true;
84 | }
85 | @Override
86 | public void onAnimationEnd(Animator animator) {
87 | if (!mCanceled) {
88 | fromView.setVisibility(View.INVISIBLE);
89 | }
90 | }
91 | })
92 | .start();
93 |
94 | toView.setAlpha(0);
95 | toView.setVisibility(View.VISIBLE);
96 | toView.animate()
97 | .setDuration(NOTE)
98 | // NOTE: We need to remove any previously set listener or Android will reuse it.
99 | .setListener(null)
100 | .alpha(1)
101 | .start();
102 | }
103 |
104 | public static List getAbsListViewCheckedItemPositions(AbsListView absListView) {
105 | SparseBooleanArray checked = absListView.getCheckedItemPositions();
106 | List positions = new ArrayList<>();
107 | int checkedSize = checked.size();
108 | for (int i = 0; i < checkedSize; ++i) {
109 | if (checked.valueAt(i)) {
110 | positions.add(checked.keyAt(i));
111 | }
112 | }
113 | return positions;
114 | }
115 |
116 | public static Drawable getActionBarBackground(Context context) {
117 | TypedValue outValue = new TypedValue();
118 | context.getTheme().resolveAttribute(android.R.attr.actionBarStyle, outValue, true);
119 | TypedArray typedArray = context.obtainStyledAttributes(outValue.resourceId,
120 | new int[] {android.R.attr.background});
121 | Drawable background = typedArray.getDrawable(0);
122 | typedArray.recycle();
123 | return background;
124 | }
125 |
126 | public static Drawable getSplitActionBarBackground(Context context) {
127 | TypedValue outValue = new TypedValue();
128 | context.getTheme().resolveAttribute(android.R.attr.actionBarStyle, outValue, true);
129 | TypedArray typedArray = context.obtainStyledAttributes(outValue.resourceId,
130 | new int[] {android.R.attr.backgroundSplit});
131 | Drawable background = typedArray.getDrawable(0);
132 | typedArray.recycle();
133 | return background;
134 | }
135 |
136 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
137 | public static FragmentManager getChildFragmentManagerIfAvailable(Fragment fragment) {
138 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
139 | return fragment.getChildFragmentManager();
140 | } else {
141 | return fragment.getFragmentManager();
142 | }
143 | }
144 |
145 | public static PackageInfo getPackageInfo(Context context) {
146 | try {
147 | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
148 | } catch (PackageManager.NameNotFoundException e) {
149 | // Should not happen.
150 | throw new RuntimeException(e);
151 | }
152 | }
153 |
154 | public static void installShortcut(int iconRes, int nameRes, Class> intentClass,
155 | Context context) {
156 | Intent intent = IntentUtils.makeInstallShortcutWithAction(iconRes, nameRes, intentClass,
157 | context);
158 | context.sendBroadcast(intent);
159 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
160 | // From 4.1.1_r1, launcher will not make a toast for successful shortcut installation.
161 | String message = context.getString(R.string.shortcut_installed_format,
162 | context.getString(nameRes));
163 | ToastUtils.show(message, context);
164 | }
165 | }
166 |
167 | // Set the intent for shortcut installation as result and then finish the Activity. See the
168 | // users of this method for its usage.
169 | public static void installShortcutAsActivity(Activity activity, int iconRes, int nameRes,
170 | Class> intentClass) {
171 | activity.setResult(Activity.RESULT_OK, IntentUtils.makeInstallShortcut(iconRes, nameRes,
172 | intentClass, activity));
173 | activity.finish();
174 | }
175 |
176 | public static boolean isFragmentAttached(Fragment fragment) {
177 | return fragment.getActivity() != null;
178 | }
179 |
180 | public static View makeDoneCancelLayout(View.OnClickListener onDoneListener,
181 | View.OnClickListener onCancelListener,
182 | LayoutInflater layoutInflater) {
183 | @SuppressLint("InflateParams")
184 | LinearLayout doneDiscardLayout = (LinearLayout)layoutInflater.inflate(R.layout.done_cancel,
185 | null);
186 | doneDiscardLayout.setLayoutParams(new ActionBar.LayoutParams(
187 | ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.MATCH_PARENT));
188 | doneDiscardLayout.findViewById(R.id.done_cancel_done).setOnClickListener(onDoneListener);
189 | doneDiscardLayout.findViewById(R.id.done_cancel_cancel)
190 | .setOnClickListener(onCancelListener);
191 | return doneDiscardLayout;
192 | }
193 |
194 | public static View makeDoneCancelLayout(View.OnClickListener onDoneListener,
195 | View.OnClickListener onCancelListener,
196 | Activity activity) {
197 | return makeDoneCancelLayout(onDoneListener, onCancelListener, activity.getLayoutInflater());
198 | }
199 |
200 | // From http://developer.android.com/training/implementing-navigation/ancestral.html#NavigateUp .
201 | public static void navigateUp(Activity activity, Bundle extras) {
202 | Intent upIntent = NavUtils.getParentActivityIntent(activity);
203 | if (upIntent == null) {
204 | LogUtils.w("No parent found for activity, will just call finish(): "
205 | + activity.getClass().getName());
206 | } else {
207 | if (extras != null) {
208 | upIntent.putExtras(extras);
209 | }
210 | if (NavUtils.shouldUpRecreateTask(activity, upIntent)) {
211 | LogUtils.i("Creating new task");
212 | // This activity is NOT part of this app's task, so create a new task
213 | // when navigating up, with a synthesized back stack.
214 | TaskStackBuilder.create(activity)
215 | // Add all of this activity's parents to the back stack.
216 | .addNextIntentWithParentStack(upIntent)
217 | // Navigate up to the closest parent.
218 | .startActivities();
219 | } else {
220 | // This activity is part of this app's task, so simply
221 | // navigate up to the logical parent activity.
222 | LogUtils.i("Using original task");
223 | // According to http://stackoverflow.com/a/14792752/2420519
224 | //NavUtils.navigateUpTo(activity, upIntent);
225 | upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
226 | activity.startActivity(upIntent);
227 | }
228 | }
229 | activity.finish();
230 | }
231 |
232 | public static void navigateUp(Activity activity) {
233 | navigateUp(activity, null);
234 | }
235 |
236 | public static void navigateUpToMainActivityWithDrawerItemId(Activity activity,
237 | int drawerItemId) {
238 | Bundle mainExtras = new Bundle();
239 | int drawerItemPosition = DrawerManager.getInstance().getPosition(drawerItemId);
240 | mainExtras.putInt(MainActivity.EXTRA_DRAWER_POSITION, drawerItemPosition);
241 | AppUtils.navigateUp(activity, mainExtras);
242 | }
243 |
244 | // Should be called after startActivity() and in onPause().
245 | public static void overrideActivityTransition(Activity activity,
246 | ActivityTransitionType type) {
247 | int enterAnim, exitAnim;
248 | switch (type) {
249 | case ACTIVITY_OPEN:
250 | enterAnim = R.anim.activity_open_enter_holo;
251 | exitAnim = R.anim.activity_open_exit_holo;
252 | break;
253 | case ACTIVITY_CLOSE:
254 | enterAnim = R.anim.activity_close_enter_holo;
255 | exitAnim = R.anim.activity_close_exit_holo;
256 | break;
257 | case SLIDE_IN_UP:
258 | enterAnim = R.anim.slide_in_up;
259 | exitAnim = R.anim.remain;
260 | break;
261 | case SLIDE_OUT_DOWN:
262 | enterAnim = R.anim.remain;
263 | exitAnim = R.anim.slide_out_down;
264 | break;
265 | case FADE:
266 | enterAnim = R.anim.activity_fade_in;
267 | exitAnim = R.anim.activity_fade_out;
268 | break;
269 | default:
270 | throw new IllegalArgumentException("Unknown activity transition type: " + type);
271 | }
272 | activity.overridePendingTransition(enterAnim, exitAnim);
273 | }
274 |
275 | public static void overrideActivityTransitionForCompat(Activity activity, boolean isOpen) {
276 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
277 | int enterAnim, exitAnim;
278 | if (isOpen) {
279 | enterAnim = R.anim.activity_open_enter_compat;
280 | exitAnim = R.anim.activity_open_exit_compat;
281 | } else {
282 | enterAnim = R.anim.activity_close_enter_compat;
283 | exitAnim = R.anim.activity_close_exit_compat;
284 | }
285 | activity.overridePendingTransition(enterAnim, exitAnim);
286 | }
287 | }
288 |
289 | public static void post(Runnable runnable) {
290 | new Handler().post(runnable);
291 | }
292 |
293 | public static void postDelayed(Runnable runnable, long delayMillis) {
294 | new Handler().postDelayed(runnable, delayMillis);
295 | }
296 |
297 | public static void postOnPreDraw(final View view, final Runnable runnable) {
298 | view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
299 | @Override
300 | public boolean onPreDraw() {
301 | view.getViewTreeObserver().removeOnPreDrawListener(this);
302 | runnable.run();
303 | return true;
304 | }
305 | });
306 | }
307 |
308 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
309 | public static void removeAllCookies() {
310 | CookieManager cookieManager = CookieManager.getInstance();
311 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
312 | cookieManager.removeAllCookies(null);
313 | } else {
314 | //noinspection deprecation
315 | cookieManager.removeAllCookie();
316 | }
317 | }
318 |
319 | public static void restartActivity(Activity activity) {
320 | activity.recreate();
321 | }
322 |
323 | @Deprecated
324 | public static void restartApplication(Context context) {
325 | // NOTE: Flag NEW_TASK is required for CLEAR_TASK, and the latter one can clear our tasks so
326 | // that Samsumg ROMs will not automatically recreate our root activity when we exit(0).
327 | Intent intent = new Intent(context, RestartApplicationActivity.class)
328 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
329 | context.startActivity(intent);
330 | }
331 |
332 | public static void restartApplicationDelayed(final Context context) {
333 | postDelayed(new Runnable() {
334 | @Override
335 | public void run() {
336 | //noinspection deprecation
337 | restartApplication(context);
338 | }
339 | }, RESTART_APPLICATION_DELAY);
340 | }
341 |
342 | public static void setAbsListViewAllItemsChecked(AbsListView absListView) {
343 | int count = absListView.getCount();
344 | for (int position = 0; position < count; ++position) {
345 | absListView.setItemChecked(position, true);
346 | }
347 | }
348 |
349 | @TargetApi(Build.VERSION_CODES.KITKAT)
350 | public static void setAlarmExact(Context context, int type, long triggerAtMillis,
351 | PendingIntent pendingIntent) {
352 | AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
353 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
354 | alarmManager.setExact(type, triggerAtMillis, pendingIntent);
355 | } else {
356 | alarmManager.set(type, triggerAtMillis, pendingIntent);
357 | }
358 | }
359 |
360 | // NOTE: Can be used to enable / disable a BroadcastReceiver.
361 | public static void setComponentEnabled(Class> componentClass, boolean enabled,
362 | Context context) {
363 | ComponentName componentName = new ComponentName(context, componentClass);
364 | PackageManager packageManager = context.getPackageManager();
365 | int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
366 | : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
367 | packageManager.setComponentEnabledSetting(componentName, state,
368 | PackageManager.DONT_KILL_APP);
369 | }
370 |
371 | // Workaround stupid Spinner, always fire onItemSelected().
372 | public static void setSpinnerSelection(Spinner spinner, int position) {
373 | if (spinner.getSelectedItemPosition() != position) {
374 | spinner.setSelection(position);
375 | } else {
376 | spinner.getOnItemSelectedListener().onItemSelected(spinner, spinner.getSelectedView(),
377 | position, spinner.getAdapter().getItemId(position));
378 | }
379 | }
380 |
381 | public static void setStatusBarColor(Window window, int color) {
382 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
383 | window.setStatusBarColor(color);
384 | }
385 | }
386 |
387 | public static void setStatusBarColorRes(Window window, int colorId, Context context) {
388 | setStatusBarColor(window, context.getResources().getColor(colorId));
389 | }
390 |
391 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
392 | public static void setViewBackground(View view, Drawable background) {
393 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
394 | view.setBackground(background);
395 | } else {
396 | //noinspection deprecation
397 | view.setBackgroundDrawable(background);
398 | }
399 | }
400 |
401 | public static void setViewLayoutParamsHeight(View view, int height) {
402 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
403 | layoutParams.height = height;
404 | view.setLayoutParams(layoutParams);
405 | }
406 |
407 | public static void setupActionBar(ActionBar actionBar) {
408 | actionBar.setDisplayOptions(ACTIONBAR_DISPLAY_OPTIONS);
409 | actionBar.setHomeButtonEnabled(true);
410 | // NOTE:
411 | // There is a bug in the handling of ActionView (SearchView) icon inside ActionBarImpl, so
412 | // we have to workaround it by setting the icon to logo programmatically.
413 | actionBar.setIcon(R.drawable.logo);
414 | }
415 |
416 | public static void setupActionBar(Activity activity) {
417 | setupActionBar(activity.getActionBar());
418 | }
419 |
420 | public static void setupActionBarIfHas(Activity activity) {
421 | ActionBar actionBar = activity.getActionBar();
422 | if (actionBar != null) {
423 | setupActionBar(actionBar);
424 | }
425 | }
426 |
427 | public static void setupActionBarNoUp(ActionBar actionBar) {
428 | actionBar.setDisplayOptions(ACTIONBAR_DISPLAY_OPTIONS_NO_UP);
429 | actionBar.setHomeButtonEnabled(false);
430 | actionBar.setIcon(R.drawable.logo);
431 | }
432 |
433 | public static void setupActionBarNoUp(Activity activity) {
434 | setupActionBarNoUp(activity.getActionBar());
435 | }
436 |
437 | public static void setupActionBarDoneCancel(Activity activity,
438 | View.OnClickListener onDoneListener,
439 | View.OnClickListener onCancelListener) {
440 | ActionBar actionBar = activity.getActionBar();
441 | actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
442 | actionBar.setCustomView(makeDoneCancelLayout(onDoneListener, onCancelListener, activity));
443 | }
444 |
445 | public static void startActivity(Intent intent, Context context) {
446 | try {
447 | context.startActivity(intent);
448 | } catch (ActivityNotFoundException e) {
449 | e.printStackTrace();
450 | ToastUtils.show(R.string.activity_not_found, context);
451 | }
452 | }
453 |
454 | public static void startActivityForResult(Activity activity, Intent intent, int requestCode) {
455 | try {
456 | activity.startActivityForResult(intent, requestCode);
457 | } catch (ActivityNotFoundException e) {
458 | e.printStackTrace();
459 | ToastUtils.show(R.string.activity_not_found, activity);
460 | }
461 | }
462 |
463 | // NOTE: ListView should make hasStableIds() return true for transition to apply.
464 | @TargetApi(Build.VERSION_CODES.KITKAT)
465 | public static void startDelayedTransitionIfAvailable(ViewGroup viewGroup, Context context) {
466 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
467 | TransitionManager.beginDelayedTransition(viewGroup, new CrossFade(context));
468 | }
469 | }
470 |
471 | public static void swapViewGroupChildren(ViewGroup viewGroup, View firstView, View secondView) {
472 | int firstIndex = viewGroup.indexOfChild(firstView);
473 | int secondIndex = viewGroup.indexOfChild(secondView);
474 | if (firstIndex < secondIndex) {
475 | viewGroup.removeViewAt(secondIndex);
476 | viewGroup.removeViewAt(firstIndex);
477 | viewGroup.addView(secondView, firstIndex);
478 | viewGroup.addView(firstView, secondIndex);
479 | } else {
480 | viewGroup.removeViewAt(firstIndex);
481 | viewGroup.removeViewAt(secondIndex);
482 | viewGroup.addView(firstView, secondIndex);
483 | viewGroup.addView(secondView, firstIndex);
484 | }
485 | }
486 |
487 |
488 | private AppUtils() {}
489 |
490 |
491 | @TargetApi(Build.VERSION_CODES.KITKAT)
492 | private static class CrossFade extends TransitionSet {
493 |
494 | public CrossFade(Context context) {
495 | setOrdering(ORDERING_TOGETHER);
496 | int duration = context.getResources().getInteger(
497 | android.R.integer.config_shortAnimTime);
498 | setDuration(duration);
499 | addTransition(new Fade(Fade.OUT));
500 | addTransition(new Fade(Fade.IN));
501 | }
502 | }
503 | }
504 |
--------------------------------------------------------------------------------