content: /*
   * Copyright 2011 - AndroidQuery.com (tinyeeliu@gmail.com)
   * 
   * 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.androidquery.util;
  
  import java.io.ByteArrayOutputStream;
  import java.io.Closeable;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.lang.Thread.UncaughtExceptionHandler;
  import java.lang.reflect.Method;
  import java.math.BigInteger;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.Executors;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.TimeUnit;
  
  import android.app.Application;
  import android.content.Context;
  import android.content.SharedPreferences.Editor;
  import android.content.res.Resources;
  import android.os.AsyncTask;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.Looper;
  import android.util.DisplayMetrics;
  import android.util.Log;
  import android.util.TypedValue;
  import android.view.View;
  import android.view.animation.AlphaAnimation;
  
  import com.androidquery.AQuery;
  
  /**
   * Utility methods. Warning: Methods might changed in future versions.
   *
   */
  
  public class AQUtility {
  
  	private static boolean debug = false;
  	private static Object wait;
  	
  	public static void setDebug(boolean debug){
  		AQUtility.debug = debug;
  	}
  	
  	public static boolean isDebug(){
  		return debug;
  	}
  	
  	public static void debugWait(long time){
  		
  		if(!debug) return;
  		
  		if(wait == null) wait = new Object();
  		
  		synchronized(wait) {
  			
  			try {
  				wait.wait(time);
  			} catch (InterruptedException e) {
  				e.printStackTrace();
  			}
  		}
  		
  	}
  	
  	
  	
  	public static void debugNotify(){
  		
  		if(!debug || wait == null) return;
  		
  		synchronized(wait) {
  			wait.notifyAll();			
  		}
  		
  	}
  	
  	
  	public static void debug(Object msg){
  		if(debug){
  			Log.w("AQuery", msg + "");
  		}
  	}
  	
  	public static void warn(Object msg, Object msg2){
  		Log.w("AQuery", msg + ":" + msg2);
  	}
  	
  	public static void debug(Object msg, Object msg2){
  		if(debug){
  			Log.w("AQuery", msg + ":" + msg2);
  		}
  	}
  	
  	public static void debug(Throwable e){
  		if(debug){
  			String trace = Log.getStackTraceString(e);
  			Log.w("AQuery", trace);
  		}
  	}
  	
  	public static void report(Throwable e){
  		
  		if(e == null) return;
  
  		try{
  
  			//debug(e);
  			warn("reporting", Log.getStackTraceString(e));
  			
  			if(eh != null){
  				eh.uncaughtException(Thread.currentThread(), e);
  			}
  			
  		}catch(Exception ex){
  			ex.printStackTrace();
  		}
  		
  	}
  	
  	private static UncaughtExceptionHandler eh;
  	public static void setExceptionHandler(UncaughtExceptionHandler handler){
  		eh = handler;
  	}
  	
  	private static Map<String, Long> times = new HashMap<String, Long>();
  	public static void time(String tag){
  		
  		times.put(tag, System.currentTimeMillis());
  		
  	}
  	
  	public static long timeEnd(String tag, long threshold){
  		
  		
  		Long old = times.get(tag);
  		if(old == null) return 0;
  		
  		long now = System.currentTimeMillis();
  		
  		long diff = now - old;
  		
  		if(threshold == 0 || diff > threshold){
  			debug(tag, diff);
  		}
  		
  		return diff;
  		
  		
  	}
  	
  	public static Object invokeHandler(Object handler, String callback, boolean fallback, boolean report, Class<?>[] cls, Object... params){
      	
  		return invokeHandler(handler, callback, fallback, report, cls, null, params);
  		
      }
  
  	public static Object invokeHandler(Object handler, String callback, boolean fallback, boolean report, Class<?>[] cls, Class<?>[] cls2, Object... params){
  		try {
  			return invokeMethod(handler, callback, fallback, cls, cls2, params);
  		} catch (Exception e) {
  			if(report){
  				AQUtility.report(e);
  			}else{
  				AQUtility.debug(e);
  			}
  			return null;
  		}
  	}
  	
  	
  	
  	
  	private static Object invokeMethod(Object handler, String callback, boolean fallback, Class<?>[] cls, Class<?>[] cls2, Object... params) throws Exception{
  		
  		if(handler == null || callback == null) return null;
  		
  		Method method = null;
  		
  		try{   
  			if(cls == null) cls = new Class[0];
  			method = handler.getClass().getMethod(callback, cls);
  			return method.invoke(handler, params);			
  		}catch(NoSuchMethodException e){
  			//AQUtility.debug(e.getMessage());
  		}
  		
  		
  		try{
  			if(fallback){
  			
  				if(cls2 == null){
  					method = handler.getClass().getMethod(callback);	
  					return method.invoke(handler);
  				}else{
  					method = handler.getClass().getMethod(callback, cls2);
  					return method.invoke(handler, params);	
  				}
  				
  			}
  		}catch(NoSuchMethodException e){
  		}
  		
  		return null;
  		
  	}
  	
  	public static void transparent(View view, boolean transparent){
  		
  		float alpha = 1;
  		if(transparent) alpha = 0.5f;
  		
  		setAlpha(view, alpha);
  		
  	}
  	
  	
  	private static void setAlpha(View view, float alphaValue){
  		
      	if(alphaValue == 1){
      		view.clearAnimation();
      	}else{
      		AlphaAnimation alpha = new AlphaAnimation(alphaValue, alphaValue);
          	alpha.setDuration(0); // Make animation instant
          	alpha.setFillAfter(true); // Tell it to persist after the animation ends    	
          	view.startAnimation(alpha);
      	}
  		
  	}
  	
  	public static void ensureUIThread(){
      	
      	if(!isUIThread()){
      		AQUtility.report(new IllegalStateException("Not UI Thread"));
      	}
      	
      }
  	
  	public static boolean isUIThread(){
      	
      	long uiId = Looper.getMainLooper().getThread().getId();
      	long cId = Thread.currentThread().getId();
      	
      	return uiId == cId;
      	
      	
      }
  	
  	
  	private static Handler handler;
  	public static Handler getHandler(){
  		if(handler == null){			
  			handler = new Handler(Looper.getMainLooper());			
  		}
  		return handler;
  	}
  	
  	public static void post(Runnable run){
  		getHandler().post(run);
  	}
  	
  	public static void post(Object handler, String method){
  		post(handler, method, new Class[0]);
  	}
  	
  	
  	public static void post(final Object handler, final String method, final Class<?>[] sig, final Object... params){
  		post(new Runnable() {
  			
  			@Override
  			public void run() {
  				
  				AQUtility.invokeHandler(handler, method, false, true, sig, params);
  				
  			}
  		});
  	}
  	
  	public static void postAsync(final Runnable run){
  		
  		AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>(){
  			
  			@Override
  			protected String doInBackground(Void... params) {
  				
  				try{
  					run.run();
  				}catch(Exception e){
  					AQUtility.report(e);
  				}
  				
  				
  				return null;
  			}
  			
  		};
  		
  		task.execute();
  		
  	}
  	
  	
  	public static void postAsync(Object handler, String method){
  		postAsync(handler, method, new Class[0]);
  	}
  	
  	public static void postAsync(final Object handler, final String method, final Class<?>[] sig, final Object... params){
  		
  		postAsync(new Runnable() {
  			
  			@Override
  			public void run() {
  				
  				AQUtility.invokeHandler(handler, method, false, true, sig, params);
  				
  			}
  		});
  	}
  	
  	public static void removePost(Runnable run){
  		getHandler().removeCallbacks(run);
  	}
  	
  	public static void postDelayed(Runnable run, long delay){
  		getHandler().postDelayed(run, delay);
  	}
  	
  	public static void apply(Editor editor){
  		
  		if(AQuery.SDK_INT >= 9){
  			AQUtility.invokeHandler(editor, "apply", false, true, null, (Object[]) null);
  		}else{
  			editor.commit();
  		}
  		
  	}
  	
  	private static String getMD5Hex(String str){
  		byte[] data = getMD5(str.getBytes());
  		
  		BigInteger bi = new BigInteger(data).abs();
  	
  		String result = bi.toString(36);
  		return result;
  	}
  	
  	
  	private static byte[] getMD5(byte[] data){
  
  		MessageDigest digest;
  		try {
  			digest = java.security.MessageDigest.getInstance("MD5");
  			digest.update(data);
  		    byte[] hash = digest.digest();
  		    return hash;
  		} catch (NoSuchAlgorithmException e) {
  			AQUtility.report(e);
  		}
  	    
  		return null;
  
  	}
  	
      private static final int IO_BUFFER_SIZE = 1024 * 4;
      public static void copy(InputStream in, OutputStream out) throws IOException {
      	copy(in, out, 0, null);
      }
      
      public static boolean TEST_IO_EXCEPTION = false;
      
      public static void copy(InputStream in, OutputStream out, int max, Progress progress) throws IOException {
      	
      	if(progress != null){
      		progress.reset();
      		progress.setBytes(max);
      	}
      	
      	byte[] b = new byte[IO_BUFFER_SIZE];
          int read;
          int count = 0;
          
          while((read = in.read(b)) != -1){
              out.write(b, 0, read);
              
              count++;
              
              if(TEST_IO_EXCEPTION && count > 2){
                  AQUtility.debug("simulating internet error");
                  throw new IOException();
              }
              
              if(progress != null){
              	progress.increment(read);
              }
          }
          
          if(progress != null){
          	progress.done();
          }
      	
      }
      
      public static byte[] toBytes(InputStream is){
      	
      	byte[] result = null;
      	
      	ByteArrayOutputStream baos = new ByteArrayOutputStream();
      	
      	try {
  			copy(is, baos);			
  			result = baos.toByteArray();
  		} catch (IOException e){
  			AQUtility.report(e);
  		}
  		
  		close(is);
      	
   	
      	return result;
      	
      }
  
      public static void write(File file, byte[] data){
      	
  	    try{
  	    	if(!file.exists()){
  	    		try{
  	    			file.createNewFile();
  	    		}catch(Exception e){
  	    			AQUtility.debug("file create fail", file);
  	    			AQUtility.report(e);
  	    		}
  	    	}
  	    	
  	    	FileOutputStream fos = new FileOutputStream(file);
  	    	fos.write(data);
  	    	fos.close();
      	}catch(Exception e){
      		AQUtility.report(e);
      	}
      	
      }
      
      public static void close(Closeable c){
      	try{
      		if(c != null){
      			c.close();
      		}
      	}catch(Exception e){   		
      	}
      }
     
  	
  	private static ScheduledExecutorService storeExe;
  	private static ScheduledExecutorService getFileStoreExecutor(){
  		
  		if(storeExe == null){
  			storeExe = Executors.newSingleThreadScheduledExecutor();
  		}
  		
  		return storeExe;
  	}
  	
  	
  	public static void storeAsync(File file, byte[] data, long delay){
  				
  		ScheduledExecutorService exe = getFileStoreExecutor();
  		
  		Common task = new Common().method(Common.STORE_FILE, file, data);
  		exe.schedule(task, delay, TimeUnit.MILLISECONDS);
  	
  	}
  	
  	private static File cacheDir;
  	private static File pcacheDir;
  	
  	public static File getCacheDir(Context context, int policy){
  		
  		if(policy == AQuery.CACHE_PERSISTENT){
  			
  			if(pcacheDir != null) return pcacheDir;
  			
  			File cd = getCacheDir(context);
  			pcacheDir = new File(cd, "persistent");
  			pcacheDir.mkdirs();
  			
  			return pcacheDir;
  		}else{
  			return getCacheDir(context);
  		}
  		
  	}
  	
  	public static File getCacheDir(Context context){			
  		
  		if(cacheDir == null){
  			cacheDir = new File(context.getCacheDir(), "aquery");
  			cacheDir.mkdirs();
  		}	
  		
  		return cacheDir;
  		
  	}
  	
  	public static void setCacheDir(File dir){
  		cacheDir = dir;
  		if(cacheDir != null){
  			cacheDir.mkdirs();
  		}
  	}
  	
  	
  	private static File makeCacheFile(File dir, String name){
  				
  		File result = new File(dir, name);		
  		return result;
  	}
  	
  	private static String getCacheFileName(String url){
  		
  		String hash = getMD5Hex(url);
  		return hash;
  	}
  	
  	public static File getCacheFile(File dir, String url){
  		if(url == null) return null;
  		if(url.startsWith(File.separator)){
  			return new File(url);
  		}
  		
  		String name = getCacheFileName(url);
  		File file = makeCacheFile(dir, name);
  		return file;
  	}
  	
  	public static File getExistedCacheByUrl(File dir, String url){
  		
  		File file = getCacheFile(dir, url);
  		if(file == null || !file.exists() || file.length() == 0){
  			return null;
  		}
  		return file;
  	}
  	
  	public static File getExistedCacheByUrlSetAccess(File dir, String url){
  		File file = getExistedCacheByUrl(dir, url);
  		if(file != null){
  			lastAccess(file);
  		}
  		return file;
  	}
  	
  	private static void lastAccess(File file){
  		long now = System.currentTimeMillis();		
  		file.setLastModified(now);
  	}
  	
  	public static void store(File file, byte[] data){
  		
  		try{
  			
  			if(file != null){			
  				AQUtility.write(file, data);
  			}
  		}catch(Exception e){
  			AQUtility.report(e);
  		}
  		
  		
  	}
  	
  	public static void cleanCacheAsync(Context context){
  		
  		long triggerSize = 3000000;
  		long targetSize = 2000000;	
  		cleanCacheAsync(context, triggerSize, targetSize);
  	}
  	
  	public static void cleanCacheAsync(Context context, long triggerSize, long targetSize){
  		
  		try{			
  			File cacheDir = getCacheDir(context);
  			
  			Common task = new Common().method(Common.CLEAN_CACHE, cacheDir, triggerSize, targetSize);
  			
  			ScheduledExecutorService exe = getFileStoreExecutor();	
  			
  			exe.schedule(task, 0, TimeUnit.MILLISECONDS);
  			
  		}catch(Exception e){
  			AQUtility.report(e);
  		}
  	}
  	
  	public static void cleanCache(File cacheDir, long triggerSize, long targetSize){
  		
  		try{
  		
  			File[] files = cacheDir.listFiles();
  			
  			if(files == null) return;
  			
  			Arrays.sort(files, new Common());
  			
  			if(testCleanNeeded(files, triggerSize)){
  				cleanCache(files, targetSize);
  			}
  			
  			File temp = getTempDir();
  			if(temp != null && temp.exists()){
  				cleanCache(temp.listFiles(), 0);
  			}
  		}catch(Exception e){
  			AQUtility.report(e);
  		}
  	}
  	
  	public static File getTempDir(){
  		File ext = Environment.getExternalStorageDirectory();
  		File tempDir = new File(ext, "aquery/temp");		
  		tempDir.mkdirs();
  		if(!tempDir.exists() || !tempDir.canWrite()){
  		    return null;
  		}
  		return tempDir;
  	}
  	
  	private static boolean testCleanNeeded(File[] files, long triggerSize){
  		
  		long total = 0;
  		
  		for(File f: files){
  			total += f.length();
  			if(total > triggerSize){
  				return true;
  			}
  		}
  		
  		return false;
  	}
  	
  	private static void cleanCache(File[] files, long maxSize){
  		
  		long total = 0;
  		int deletes = 0;
  		
  		for(int i = 0; i < files.length; i++){
  			
  			File f = files[i];
  			
  			if(f.isFile()){
  			
  				total += f.length();
  				
  				if(total < maxSize){
  					//ok
  				}else{				
  					f.delete();
  					deletes++;
  					//AQUtility.debug("del", f.getAbsolutePath());
  				}
  				
  			}
  		}
  		
  		AQUtility.debug("deleted" , deletes);
  	}
  	
  	public static int dip2pixel(Context context, float n){
  		int value = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, n, context.getResources().getDisplayMetrics());
  		return value;
  	}
  	
  	public static float pixel2dip(Context context, float n){
  	    Resources resources = context.getResources();
  	    DisplayMetrics metrics = resources.getDisplayMetrics();
  	    float dp = n / (metrics.densityDpi / 160f);
  	    return dp;
  
  	}
  	
  	private static Context context;
  	public static void setContext(Application app){
  		context = app.getApplicationContext();
  	}
  	
  	public static Context getContext(){
  		if(context == null){
  			AQUtility.warn("warn", "getContext with null");
  			AQUtility.debug(new IllegalStateException());
  		}
  		return context;
  	}
  	
      // Mapping table from 6-bit nibbles to Base64 characters.
      private static final char[] map1 = new char[64];
         static {
            int i=0;
            for (char c='A'; c<='Z'; c++) map1[i++] = c;
            for (char c='a'; c<='z'; c++) map1[i++] = c;
            for (char c='0'; c<='9'; c++) map1[i++] = c;
            map1[i++] = '+'; map1[i++] = '/'; }
  
      // Mapping table from Base64 characters to 6-bit nibbles.
      private static final byte[] map2 = new byte[128];
         static {
            for (int i=0; i<map2.length; i++) map2[i] = -1;
            for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }
      
      //Source: http://www.source-code.biz/base64coder/java/Base64Coder.java.txt
      public static char[] encode64(byte[] in, int iOff, int iLen) {
          
         int oDataLen = (iLen*4+2)/3;       // output length without padding
         int oLen = ((iLen+2)/3)*4;         // output length including padding
         char[] out = new char[oLen];
         int ip = iOff;
         int iEnd = iOff + iLen;
         int op = 0;
         while (ip < iEnd) {
            int i0 = in[ip++] & 0xff;
            int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
            int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
            int o0 = i0 >>> 2;
            int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
            int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
            int o3 = i2 & 0x3F;
            out[op++] = map1[o0];
            out[op++] = map1[o1];
            out[op] = op < oDataLen ? map1[o2] : '='; op++;
            out[op] = op < oDataLen ? map1[o3] : '='; op++; }
         return out; 
      }
  }
  