package com.simpligility.maven.plugins.android.common;

import com.google.common.io.PatternFilenameFilter;
import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
import com.simpligility.maven.plugins.android.AndroidNdk;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
import org.apache.maven.artifact.resolver.filter.OrArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;

import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;

/**
 * @author Johan Lindquist
 */
public class NativeHelper
{

    public static final int NDK_REQUIRED_VERSION = 7;

    private MavenProject project;
    private DependencyGraphBuilder dependencyGraphBuilder;
    private Log log;

    public NativeHelper( MavenProject project, DependencyGraphBuilder dependencyGraphBuilder, Log log )
    {
        this.project = project;
        this.dependencyGraphBuilder = dependencyGraphBuilder;
        this.log = log;
    }

    public Set<Artifact> getNativeDependenciesArtifacts(
            AbstractAndroidMojo mojo, File unpackDirectory, boolean sharedLibraries )
            throws MojoExecutionException
    {
        log.debug( "Finding native dependencies. UnpackFolder=" + unpackDirectory + " shared=" + sharedLibraries );
        final Set<Artifact> filteredArtifacts = new LinkedHashSet<Artifact>();
        final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>();

        // Add all dependent artifacts declared in the pom file
        // Note: The result of project.getDependencyArtifacts() can be an UnmodifiableSet so we
        //       have created our own above and add to that.
        allArtifacts.addAll( project.getDependencyArtifacts() );

        // Add all transitive artifacts as well
        // this allows armeabi classifier -> apklib -> apklib -> apk chaining to only include armeabi in APK
        allArtifacts.addAll( project.getArtifacts() );

        for ( Artifact artifact : allArtifacts )
        {
            final boolean isNativeLibrary = isNativeLibrary( sharedLibraries, artifact.getType() );

            log.debug( "Checking artifact : " + artifact );
            // A null value in the scope indicates that the artifact has been attached
            // as part of a previous build step (NDK mojo)
            if ( isNativeLibrary && artifact.getScope() == null )
            {
                // Including attached artifact
                log.debug( "Including attached artifact: " + artifact + ". Artifact scope is not set." );
                filteredArtifacts.add( artifact );
            }
            else
            {
                if ( isNativeLibrary && (
                        Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_RUNTIME
                                .equals( artifact.getScope() ) ) )
                {
                    log.debug( "Including attached artifact: " + artifact + ". Artifact scope is Compile or Runtime." );
                    filteredArtifacts.add( artifact );
                }
                else
                {
                    final String type = artifact.getType();

                    if ( APKLIB.equals( type ) || AAR.equals( type ) )
                    {
                        // Check if the artifact contains a libs folder - if so, include it in the list
                        final File libsFolder;
                        if ( mojo != null )
                        {
                            libsFolder = mojo.getUnpackedLibNativesFolder( artifact );
                        }
                        else
                        {
                            // This is used from NativeHelperTest since we have no AbstractAndroidMojo there
                            libsFolder = new File(
                                AbstractAndroidMojo.getLibraryUnpackDirectory( unpackDirectory, artifact ),
                                AAR.equals( type ) ? "jni" : "libs" );
                        }

                        if ( !libsFolder.exists() )
                        {
                            log.debug( "Skipping " + libsFolder.getAbsolutePath() + " for native artifacts" );
                            continue;
                        }

                        if ( !libsFolder.isDirectory() )
                        {
                            continue;
                        }

                        log.debug( "Checking " + libsFolder.getAbsolutePath() + " for native artifacts" );
                        // make sure we ignore libs folders that only contain JARs
                        // The regular expression filters out all file paths ending with '.jar' or '.JAR',
                        // so all native libs remain
                        if ( libsFolder.list( new PatternFilenameFilter( "^.*(?<!(?i)\\.jar)$" ) ).length > 0 )
                        {
                            log.debug( "Including attached artifact: " + artifact + ". Artifact is "
                                    + artifact.getType() );
                            filteredArtifacts.add( artifact );
                        }
                    }
                    else if ( ! "jar".equals( type ) )
                    {
                        log.debug( "Not checking " + type + " for native artifacts" );
                    }
                }
            }
        }

        Set<Artifact> transitiveArtifacts = processTransitiveDependencies( project.getDependencies(), sharedLibraries );

        filteredArtifacts.addAll( transitiveArtifacts );

        return filteredArtifacts;
    }

    private boolean isNativeLibrary( boolean sharedLibraries, String artifactType )
    {
        return ( sharedLibraries
                ? Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( artifactType )
                : Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( artifactType )
        );
    }

    private Set<Artifact> processTransitiveDependencies( List<Dependency> dependencies, boolean sharedLibraries )
            throws MojoExecutionException
    {
        final Set<Artifact> transitiveArtifacts = new LinkedHashSet<Artifact>();
        for ( Dependency dependency : dependencies )
        {
            if ( ! Artifact.SCOPE_PROVIDED.equals( dependency.getScope() ) && ! dependency.isOptional() )
            {
                final Set<Artifact> transArtifactsFor = processTransitiveDependencies( dependency, sharedLibraries );
                log.debug( "Found transitive dependencies for : " + dependency + " transDeps : " + transArtifactsFor );
                transitiveArtifacts.addAll( transArtifactsFor );
            }
        }

        return transitiveArtifacts;

    }

    private Set<Artifact> processTransitiveDependencies( Dependency dependency, boolean sharedLibraries )
            throws MojoExecutionException
    {
        try
        {
            final Set<Artifact> artifacts = new LinkedHashSet<Artifact>();

            final List<String> exclusionPatterns = new ArrayList<String>();
            if ( dependency.getExclusions() != null && ! dependency.getExclusions().isEmpty() )
            {
                for ( final Exclusion exclusion : dependency.getExclusions() )
                {
                    exclusionPatterns.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() );
                }
            }
            final ArtifactFilter optionalFilter = new ArtifactFilter()
            {
                @Override
                public boolean include( Artifact artifact )
                {
                    return !artifact.isOptional();
                }
            };

            final AndArtifactFilter filter = new AndArtifactFilter();
            filter.add( new ExcludesArtifactFilter( exclusionPatterns ) );
            filter.add( new OrArtifactFilter( Arrays.<ArtifactFilter>asList( new ScopeArtifactFilter( "compile" ),
                                                                            new ScopeArtifactFilter( "runtime" ),
                                                                            new ScopeArtifactFilter( "test" ) ) ) );
            filter.add( optionalFilter );

            final DependencyNode node = dependencyGraphBuilder.buildDependencyGraph( project, filter );
            final CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
            node.accept( collectingVisitor );

            final List<DependencyNode> dependencies = collectingVisitor.getNodes();
            for ( final DependencyNode dep : dependencies )
            {
                final boolean isNativeLibrary = isNativeLibrary( sharedLibraries, dep.getArtifact().getType() );
                if ( isNativeLibrary )
                {
                    artifacts.add( dep.getArtifact() );
                }
            }

            return artifacts;
        }
        catch ( Exception e )
        {
            throw new MojoExecutionException( "Error while processing transitive dependencies", e );
        }
    }

    public static String[] getAppAbi( File applicationMakefile )
    {
        Scanner scanner = null;
        try
        {
            if ( applicationMakefile != null && applicationMakefile.exists() )
            {
                scanner = new Scanner( applicationMakefile );
                while ( scanner.hasNextLine( ) )
                {
                    String line = scanner.nextLine().trim();
                    if ( line.startsWith( "APP_ABI" ) )
                    {
                        return line.substring( line.indexOf( ":=" ) + 2 ).trim().split( " " );
                    }
                }
            }
        }
        catch ( FileNotFoundException e )
        {
            // do nothing
        }
        finally
        {
            if ( scanner != null )
            {
                scanner.close();
            }
        }
        return null;
    }

    /** Extracts, if embedded correctly, the artifacts architecture from its classifier.  The format of the
     * classifier, if including the architecture is <architecture>-<classifier>.  If no
     * architecture is embedded in the classifier, 'armeabi' will be returned.
     *
     *
     * @param artifact The artifact to retrieve the classifier from.
     * @param defaultArchitecture The architecture to return if can't be resolved from the classifier
     * @return The retrieved architecture, or defaulArchitecture if not resolveable
     */
    public static String extractArchitectureFromArtifact( Artifact artifact, final String defaultArchitecture )
    {
        String classifier = artifact.getClassifier();
        if ( classifier != null )
        {
            //
            // We loop backwards to catch the case where the classifier is
            // potentially armeabi-v7a - this collides with armeabi if looping
            // through this loop in the other direction
            //

            for ( int i = AndroidNdk.NDK_ARCHITECTURES.length - 1; i >= 0; i-- )
            {
                String ndkArchitecture = AndroidNdk.NDK_ARCHITECTURES[i];
                if ( classifier.startsWith( ndkArchitecture ) )
                {
                    return ndkArchitecture;
                }
            }

        }
        // Default case is to return the default architecture
        return defaultArchitecture;
    }

    /** Attempts to extract, from various sources, the applicable list of NDK architectures to support
     * as part of the build.
     *

     *

     * It retrieves the list from the following places:
     *

     *
ndkArchitecture parameter

     *
projects Application.mk - currently only a single architecture is supported by this method

     *

     *
     *
     * @param ndkArchitectures Space separated list of architectures.  This may be from configuration or otherwise
     * @param applicationMakefile The makefile (Application.mk) to retrieve the list from.
     * @param basedir Directory the build is running from (to resolve files)
     *
     * @return List of architectures to be supported by build.
     *
     * @throws MojoExecutionException
     */
    public static String[] getNdkArchitectures( final String ndkArchitectures, final String applicationMakefile,
                                                final File basedir )
        throws MojoExecutionException
    {
        // if there is a specified ndk architecture, return it
        if ( ndkArchitectures != null )
        {
            return ndkArchitectures.split( " " );
        }

        // if there is no application makefile specified, let's use the default one
        String applicationMakefileToUse = applicationMakefile;
        if ( applicationMakefileToUse == null )
        {
            applicationMakefileToUse = "jni/Application.mk";
        }

        // now let's see if the application file exists
        File appMK = new File( basedir, applicationMakefileToUse );
        if ( appMK.exists() )
        {
            String[] foundNdkArchitectures = getAppAbi( appMK );
            if ( foundNdkArchitectures != null )
            {
                return foundNdkArchitectures;
            }
        }

        // return a default ndk architecture
        return new String[] { "armeabi" };
    }

    /** Helper method for determining whether the specified architecture is a match for the
     * artifact using its classifier.  When used for architecture matching, the classifier must be
     * formed by <architecture>-<classifier>.
     * If the artifact is legacy and defines no valid architecture, the artifact architecture will
     * default to armeabi.
     *
     * @param ndkArchitecture Architecture to check for match
     * @param artifact Artifact to check the classifier match for
     * @return True if the architecture matches, otherwise false
     */
    public static boolean artifactHasHardwareArchitecture( Artifact artifact, String ndkArchitecture,
                                                           String defaultArchitecture )
    {
        return Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( artifact.getType() )
                && ndkArchitecture.equals( extractArchitectureFromArtifact( artifact, defaultArchitecture ) );
    }

}


package com.simpligility.maven.plugins.android.common;

import org.codehaus.plexus.util.DirectoryScanner;

import java.io.File;

/**
 * Retrieves the names of layout resource files.
 */
public final class FileRetriever
{
    private final String[] includes;

    /**
     * @param includes      Ant-style include statements, for example "** /*.aidl" (but without the space
     *                      in the middle).
     */
    public FileRetriever( String... includes )
    {
        this.includes = includes;
    }

    /**
     * Finds the files in the supplied folder that match the configured includes.
     *
     * @param baseDirectory Directory to find files in.
     * @return String[] of the files' paths and names, relative to baseDirectory. Empty
     *         String[] if baseDirectory does not exist.
     */
    public String[] getFileNames( File baseDirectory )
    {
        if ( !baseDirectory.exists() )
        {
            return new String[ 0 ];
        }

        final DirectoryScanner directoryScanner = new DirectoryScanner();
        directoryScanner.setBasedir( baseDirectory );
        directoryScanner.setIncludes( includes );
        directoryScanner.addDefaultExcludes();
        directoryScanner.scan();

        return directoryScanner.getIncludedFiles();
    }
}


/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.simpligility.maven.plugins.android;

import com.android.annotations.NonNull;
import com.android.ide.common.process.JavaProcessExecutor;
import com.android.ide.common.process.JavaProcessInfo;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessOutput;
import com.android.ide.common.process.ProcessOutputHandler;
import com.android.ide.common.process.ProcessResult;
import com.android.utils.ILogger;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
/**
 * Simple implementation of ProcessExecutor, using the standard Java Process(Builder) API.
 */
public class DefaultJavaProcessExecutor implements JavaProcessExecutor
{
    private final ILogger mLogger;
    public DefaultJavaProcessExecutor( ILogger logger )
    {
        mLogger = logger;
    }
    @NonNull
    @Override
    public ProcessResult execute(
            @NonNull JavaProcessInfo processInfo,
            @NonNull ProcessOutputHandler processOutputHandler )
    {
        List<String> command = Lists.newArrayList();
        command.add( processInfo.getExecutable() );
        command.addAll( processInfo.getArgs() );
        String commandString = Joiner.on( ' ' ).join( command );
        mLogger.info( "command: " + commandString );
        try
        {
            // launch the command line process
            ProcessBuilder processBuilder = new ProcessBuilder( command );
            Map<String, Object> envVariableMap = processInfo.getEnvironment();
            if ( !envVariableMap.isEmpty() )
            {
                Map<String, String> env = processBuilder.environment();
                for ( Map.Entry<String, Object> entry : envVariableMap.entrySet() )
                {
                    env.put( entry.getKey(), entry.getValue().toString() );
                }
            }
            // start the process
            Process process = processBuilder.start();
            // and grab the output, and the exit code
            ProcessOutput output = processOutputHandler.createOutput();
            int exitCode = grabProcessOutput( process, output );
            processOutputHandler.handleOutput( output );
            return new ProcessResultImplCopy( commandString, exitCode );
        }
        catch ( IOException e )
        {
            return new ProcessResultImplCopy( commandString, e );
        }
        catch ( InterruptedException e )
        {
            // Restore the interrupted status
            Thread.currentThread().interrupt();
            return new ProcessResultImplCopy( commandString, e );
        }
        catch ( ProcessException e )
        {
            return new ProcessResultImplCopy( commandString, e );
        }
    }
    /**
     * Get the stderr/stdout outputs of a process and return when the process is done.
     * Both <b>must</b> be read or the process will block on windows.
     *
     * @param process The process to get the output from.
     * @param output The processOutput containing where to send the output.
     *      Note that on Windows capturing the output is not optional. If output is null
     *      the stdout/stderr will be captured and discarded.
     * @return the process return code.
     * @throws InterruptedException if {@link Process#waitFor()} was interrupted.
     */
    private static int grabProcessOutput(
            @NonNull final Process process,
            @NonNull final ProcessOutput output ) throws InterruptedException
    {
        Thread threadErr = new Thread( "stderr" )
        {
            @Override
            public void run()
            {
                InputStream stderr = process.getErrorStream();
                OutputStream stream = output.getErrorOutput();
                try
                {
                    ByteStreams.copy( stderr, stream );
                    stream.flush();
                }
                catch ( IOException e )
                {
                    // ignore?
                }
                finally
                {
                    try
                    {
                        Closeables.close( stderr, true /* swallowIOException */ );
                    }
                    catch ( IOException e )
                    {
                        // cannot happen
                    }
                    try
                    {
                        Closeables.close( stream, true /* swallowIOException */ );
                    }
                    catch ( IOException e )
                    {
                        // cannot happen
                    }
                }
            }
        };
        Thread threadOut = new Thread( "stdout" )
        {
            @Override
            public void run()
            {
                InputStream stdout = process.getInputStream();
                OutputStream stream = output.getStandardOutput();
                try
                {
                    ByteStreams.copy( stdout, stream );
                    stream.flush();
                }
                catch ( IOException e )
                {
                    // ignore?
                }
                finally
                {
                    try
                    {
                        Closeables.close( stdout, true /* swallowIOException */ );
                    }
                    catch ( IOException e )
                    {
                        // cannot happen
                    }
                    try
                    {
                        Closeables.close( stream, true /* swallowIOException */ );
                    }
                    catch ( IOException e )
                    {
                        // cannot happen
                    }
                }
            }
        };
        threadErr.start();
        threadOut.start();
        // it looks like on windows process#waitFor() can return
        // before the thread have filled the arrays, so we wait for both threads and the
        // process itself.
        threadErr.join();
        threadOut.join();
        // get the return code from the process
        return process.waitFor();
    }
}


package com.qiniu.android.dns.util;

import java.nio.charset.Charset;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

public class DES {
    /**
     * Convert data to encrypted hex string
     * @param data data to encrypt
     * @param key encrypt key
     * @return hex string
     */
    public static String encrypt(String data, String key) {
        try {
            Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
            c.init(Cipher.ENCRYPT_MODE, SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(key.getBytes())));
            return Hex.encodeHexString(c.doFinal(data.getBytes()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Convert encrypted hex string to UTF-8 string
     * @param data data to decrypt
     * @param key decrypt key
     * @return UTF-8 string
     */
    public static String decrypt(String data, String key) {
        try {
            Cipher c = Cipher.getInstance("DES/ECB/PKCS5Padding");
            c.init(Cipher.DECRYPT_MODE, SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(key.getBytes())));
            return new String(c.doFinal(Hex.decodeHex(data.toCharArray())), Charset.defaultCharset());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}


package com.qiniu.android.dns.local;

import com.qiniu.android.dns.DnsException;
import com.qiniu.android.dns.Record;
import com.qiniu.android.dns.util.BitSet;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.IDN;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
//import java.util.logging.Level;
//import java.util.logging.Logger;

/**
 * reference github/rtreffer/minidns.
 */
public final class DnsMessage {

    public static byte[] buildQuery(String domain, int id) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        DataOutputStream dos = new DataOutputStream(baos);
        BitSet bits = new BitSet();

//        recursionDesired
        bits.set(8);

        try {
            dos.writeShort((short) id);
            dos.writeShort((short) bits.value());

//        questions count
            dos.writeShort(1);

//      no  answer
            dos.writeShort(0);

//      no nameserverRecords
            dos.writeShort(0);

//      no  additionalResourceRecords
            dos.writeShort(0);

            dos.flush();
            writeQuestion(baos, domain);
        } catch (IOException e) {
            throw new AssertionError(e);
        }

        return baos.toByteArray();
    }

    private static void writeDomain(OutputStream out, String domain) throws IOException {
        for (String s : domain.split("[.\u3002\uFF0E\uFF61]")) {
            byte[] buffer = IDN.toASCII(s).getBytes();
            out.write(buffer.length);
            out.write(buffer, 0, buffer.length); // ?
        }
        out.write(0);
    }

    private static void writeQuestion(OutputStream out, String domain) throws IOException {
        DataOutputStream dos = new DataOutputStream(out);
        writeDomain(out, domain);
//        type A
        dos.writeShort(1);
//        class internet
        dos.writeShort(1);
    }

    public static Record[] parseResponse(byte[] response, int id, String domain) throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(response);
        DataInputStream dis = new DataInputStream(bis);
        int answerId = dis.readUnsignedShort();
        if (answerId != id) {
            throw new DnsException(domain, "the answer id " + answerId + " is not match " + id);
        }
        int header = dis.readUnsignedShort();
        boolean recursionDesired = ((header >> 8) & 1) == 1;
        boolean recursionAvailable = ((header >> 7) & 1) == 1;
        if (!(recursionAvailable && recursionDesired)) {
            throw new DnsException(domain, "the dns server cant support recursion ");

        }

        int questionCount = dis.readUnsignedShort();
        int answerCount = dis.readUnsignedShort();
//        nameserver Count
        dis.readUnsignedShort();
//        additionalResourceRecordCount
        dis.readUnsignedShort();

//         ignore questions
        readQuestions(dis, response, questionCount);

        return readAnswers(dis, response, answerCount);
//        ignore auth
//        ignore additional
    }

    /**
     * Parse a domain name starting at the current offset and moving the input
     * stream pointer past this domain name (even if cross references occure).
     *
     * @param dis  The input stream.
     * @param data The raw data (for cross references).
     * @return The domain name string.
     * @throws IOException Should never happen.
     */
    private static String readName(DataInputStream dis, byte data[])
            throws IOException {
        int c = dis.readUnsignedByte();
        if ((c & 0xc0) == 0xc0) {
            c = ((c & 0x3f) << 8) + dis.readUnsignedByte();
            HashSet<Integer> jumps = new HashSet<Integer>();
            jumps.add(c);
            return readName(data, c, jumps);
        }
        if (c == 0) {
            return "";
        }
        byte b[] = new byte[c];
        dis.readFully(b);
        String s = IDN.toUnicode(new String(b));
        String t = readName(dis, data);
        if (t.length() > 0) {
            s = s + "." + t;
        }
        return s;
    }

    /**
     * Parse a domain name starting at the given offset.
     *
     * @param data   The raw data.
     * @param offset The offset.
     * @param jumps  The list of jumps (by now).
     * @return The parsed domain name.
     * @throws IOException on cycles.
     */
    private static String readName(
            byte data[],
            int offset,
            HashSet<Integer> jumps
    ) throws IOException {
        int c = data[offset] & 0xff;
        if ((c & 0xc0) == 0xc0) {
            c = ((c & 0x3f) << 8) + (data[offset + 1] & 0xff);
            if (jumps.contains(c)) {
                throw new DnsException("", "Cyclic offsets detected.");
            }
            jumps.add(c);
            return readName(data, c, jumps);
        }
        if (c == 0) {
            return "";
        }
        String s = new String(data, offset + 1, c);
        String t = readName(data, offset + 1 + c, jumps);
        if (t.length() > 0) {
            s = s + "." + t;
        }
        return s;
    }

    private static void readQuestions(DataInputStream dis, byte[] data, int count) throws IOException {
        while (count-- > 0) {
            readName(dis, data);
//            type
            dis.readUnsignedShort();
//            class
            dis.readUnsignedShort();
        }
    }

    private static Record[] readAnswers(DataInputStream dis, byte[] data, int count) throws IOException {
        int offset = 0;
        Record[] ret = new Record[count];
        while (count-- > 0) {
            ret[offset++] = readRecord(dis, data);
        }
        return ret;
    }

    private static Record readRecord(DataInputStream dis, byte[] data) throws IOException {
        readName(dis, data);
        int type = dis.readUnsignedShort();
//            class
        dis.readUnsignedShort();

        long ttl = (((long) dis.readUnsignedShort()) << 16) +
                dis.readUnsignedShort();
        int payloadLength = dis.readUnsignedShort();
        String payload = null;
        switch (type) {
            case Record.TYPE_A:
                byte[] ip = new byte[4];
                dis.readFully(ip);
                payload = InetAddress.getByAddress(ip).getHostAddress();
                break;
            case Record.TYPE_CNAME:
                payload = readName(dis, data);
                break;
            default:
                payload = null;
                for (int i = 0; i < payloadLength; i++) {
                    dis.readByte();
                }
                break;
        }
        if (payload == null) {
            throw new UnknownHostException("no record");
        }
        return new Record(payload, type, (int) ttl, System.currentTimeMillis() / 1000);
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.qiniu.android.dns.util;

import java.io.IOException;

/**
 * Converts hexadecimal Strings.
 * This class is thread-safe.
 *
 * @version $Id: Hex.java 1619948 2014-08-22 22:53:55Z ggregory $
 * @since 1.1
 */
public final class Hex {

    /**
     * Used to build output as Hex
     */
    private static final char[] DIGITS_LOWER =
            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * Used to build output as Hex
     */
    private static final char[] DIGITS_UPPER =
            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
     * returned array will be half the length of the passed array, as it takes two characters to represent any given
     * byte. An exception is thrown if the passed char array has an odd number of elements.
     *
     * @param data An array of characters containing hexadecimal digits
     * @return A byte array containing binary data decoded from the supplied char array.
     * @throws HexDecodeException Thrown if an odd number or illegal of characters is supplied
     */
    public static byte[] decodeHex(final char[] data) throws HexDecodeException {

        final int len = data.length;

        if ((len & 0x01) != 0) {
            throw new HexDecodeException("Odd number of characters.");
        }

        final byte[] out = new byte[len >> 1];

        // two characters form the hex value.
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f = f | toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     * The returned array will be double the length of the passed array, as it takes two characters to represent any
     * given byte.
     *
     * @param data a byte[] to convert to Hex characters
     * @return A char[] containing hexadecimal characters
     */
    public static char[] encodeHex(final byte[] data) {
        return encodeHex(data, true);
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     * The returned array will be double the length of the passed array, as it takes two characters to represent any
     * given byte.
     *
     * @param data        a byte[] to convert to Hex characters
     * @param toLowerCase true converts to lowercase, false to uppercase
     * @return A char[] containing hexadecimal characters
     * @since 1.4
     */
    public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     * The returned array will be double the length of the passed array, as it takes two characters to represent any
     * given byte.
     *
     * @param data     a byte[] to convert to Hex characters
     * @param toDigits the output alphabet
     * @return A char[] containing hexadecimal characters
     * @since 1.4
     */
    private static char[] encodeHex(final byte[] data, final char[] toDigits) {
        final int l = data.length;
        final char[] out = new char[l << 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
        return out;
    }

    /**
     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
     *
     * @param data a byte[] to convert to Hex characters
     * @return A String containing hexadecimal characters
     * @since 1.4
     */
    public static String encodeHexString(final byte[] data) {
        return new String(encodeHex(data));
    }

    /**
     * Converts a hexadecimal character to an integer.
     *
     * @param ch    A character to convert to an integer digit
     * @param index The index of the character in the source
     * @return An integer
     * @throws HexDecodeException Thrown if ch is an illegal hex character
     */
    protected static int toDigit(final char ch, final int index) throws HexDecodeException {
        final int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new HexDecodeException("Illegal hexadecimal character " + ch + " at index " + index);
        }
        return digit;
    }

    public static class HexDecodeException extends IOException {
        public HexDecodeException(String msg) {
            super(msg);
        }
    }
}


package com.qiniu.android.dns;

import android.os.Build;

import com.qiniu.android.dns.http.DomainNotOwn;
import com.qiniu.android.dns.local.Hosts;
import com.qiniu.android.dns.util.LruCache;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * DNS解析管理类，可以重复使用
 */
public final class DnsManager {

    private final IResolver[] resolvers;
    private final LruCache<String, Record[]> cache;
    private final Hosts hosts = new Hosts();
    private final IpSorter sorter;
    private volatile NetworkInfo info = null;
    private volatile int index = 0;

    /**
     * @param info      当前的网络信息，从Android context中获取
     * @param resolvers 具体的dns 解析示例，可以是local或者httpdns
     */
    public DnsManager(NetworkInfo info, IResolver[] resolvers) {
        this(info, resolvers, null);
    }

    /**
     * @param info      当前的网络信息，从Android context中获取
     * @param resolvers 具体的dns 解析示例，可以是local或者httpdns
     * @param sorter    外部接口，对获取的IP数组进行排序
     */
    public DnsManager(NetworkInfo info, IResolver[] resolvers, IpSorter sorter) {
        this.info = info == null ? NetworkInfo.normal : info;
        this.resolvers = resolvers.clone();
        cache = new LruCache<>();
        this.sorter = sorter == null ? new DummySorter() : sorter;
    }

    private static Record[] trimCname(Record[] records) {
        ArrayList<Record> a = new ArrayList<>(records.length);
        for (Record r : records) {
            if (r != null && r.type == Record.TYPE_A) {
                a.add(r);
            }
        }
        return a.toArray(new Record[a.size()]);
    }

    private static void rotate(Record[] records) {
        if (records != null && records.length > 1) {
            Record first = records[0];
            System.arraycopy(records, 1, records, 0, records.length - 1);
            records[records.length - 1] = first;
        }
    }

    private static String[] records2Ip(Record[] records) {
        if (records == null || records.length == 0) {
            return null;
        }
        ArrayList<String> a = new ArrayList<>(records.length);
        for (Record r : records) {
            a.add(r.value);
        }
        if (a.size() == 0) {
            return null;
        }
        return a.toArray(new String[a.size()]);
    }

    public static boolean validIP(String ip) {
        if (ip == null || ip.length() < 7 || ip.length() > 15) return false;
        if (ip.contains("-")) return false;

        try {
            int x = 0;
            int y = ip.indexOf('.');

            if (y != -1 && Integer.parseInt(ip.substring(x, y)) > 255) return false;

            x = ip.indexOf('.', ++y);
            if (x != -1 && Integer.parseInt(ip.substring(y, x)) > 255) return false;

            y = ip.indexOf('.', ++x);
            return !(y != -1 && Integer.parseInt(ip.substring(x, y)) > 255 &&
                    Integer.parseInt(ip.substring(++y, ip.length() - 1)) > 255 &&
                    ip.charAt(ip.length() - 1) != '.');

        } catch (NumberFormatException e) {
            return false;
        }
    }

    // 简单通过时区判断是否在大陆
    public static boolean needHttpDns() {
        try {
            TimeZone zone = TimeZone.getDefault();
            String id = zone.getID();
            return ("Asia/Shanghai".equals(id) || "Asia/Chongqing".equals(id) ||
                    "Asia/Harbin".equals(id) || "Asia/Urumqi".equals(id));

        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 查询域名
     *
     * @param domain 域名
     * @return ip 列表
     * @throws IOException 网络异常或者无法解析抛出异常
     */
    public String[] query(String domain) throws IOException {
        return query(new Domain(domain));
    }

    public String[] query(Domain domain) throws IOException {
        if (domain == null) {
            throw new IOException("null domain");
        }
        if (domain.domain == null || domain.domain.trim().length() == 0) {
            throw new IOException("empty domain " + domain.domain);
        }

        if (validIP(domain.domain)) {
            return new String[]{domain.domain};
        }

        String[] r = queryInternal(domain);
        if (r == null || r.length <= 1) {
            return r;
        }
        return sorter.sort(r);
    }

    /**
     * 查询域名
     *
     * @param domain 域名参数
     * @return ip 列表
     * @throws IOException 网络异常或者无法解析抛出异常
     */

    private String[] queryInternal(Domain domain) throws IOException {
//        有些手机网络状态可能不对
//        if (info.netStatus == NetworkInfo.NO_NETWORK){
//            return null;
//        }
        Record[] records = null;
        if (domain.hostsFirst) {
            String[] ret = hosts.query(domain, info);
            if (ret != null && ret.length != 0) {
                return ret;
            }
        }
        synchronized (cache) {
            if (info.equals(NetworkInfo.normal) && Network.isNetworkChanged()) {
                cache.clear();
                synchronized (resolvers) {
                    index = 0;
                }
            } else {
                records = cache.get(domain.domain);
                if (records != null && records.length != 0) {
                    if (!records[0].isExpired()) {
                        if (records.length > 1) {
                            rotate(records);
                        }
                        return records2Ip(records);
                    } else {
                        records = null;
                    }
                }
            }
        }

        IOException lastE = null;
        int firstOk = index;
        for (int i = 0; i < resolvers.length; i++) {
            int pos = (firstOk + i) % resolvers.length;
            NetworkInfo before = info;
            String ip = Network.getIp();
            try {
                records = resolvers[pos].resolve(domain, info);
            } catch (DomainNotOwn e) {
                continue;
            } catch (IOException e) {
                lastE = e;
                e.printStackTrace();
            } catch (Exception e2) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
                    lastE = new IOException(e2);
                }
                e2.printStackTrace();
            }
            String ip2 = Network.getIp();
            if (info == before && (records == null || records.length == 0) && ip.equals(ip2)) {
                synchronized (resolvers) {
                    if (index == firstOk) {
                        index++;
                        if (index == resolvers.length) {
                            index = 0;
                        }
                    }
                }
            } else {
                break;
            }
        }

        if (records == null || records.length == 0) {
            if (!domain.hostsFirst) {
                String[] rs = hosts.query(domain, info);
                if (rs != null && rs.length != 0) {
                    return rs;
                }
            }
            if (lastE != null) {
                throw lastE;
            }
            throw new UnknownHostException(domain.domain);
        }
        records = trimCname(records);
        if (records.length == 0) {
            throw new UnknownHostException("no A records");
        }
        synchronized (cache) {
            cache.put(domain.domain, records);
        }
        return records2Ip(records);
    }

    public InetAddress[] queryInetAdress(Domain domain) throws IOException {
        String[] ips = query(domain);
        InetAddress[] addresses = new InetAddress[ips.length];
        for (int i = 0; i < ips.length; i++) {
            addresses[i] = InetAddress.getByName(ips[i]);
        }
        return addresses;
    }

    /**
     * 当网络发生变化时，通知当前的网络信息
     *
     * @param info 网络信息
     */
    public void onNetworkChange(NetworkInfo info) {
        clearCache();
        this.info = info == null ? NetworkInfo.normal : info;
        synchronized (resolvers) {
            index = 0;
        }
    }

    private void clearCache() {
        synchronized (cache) {
            cache.clear();
        }
    }

    /**
     * 插入指定运营商的hosts配置
     *
     * @param domain   域名
     * @param ip       ip
     * @param provider 运营商，见 NetworkInfo
     * @return 当前的DnsManager，便于链式调用
     */
    public DnsManager putHosts(String domain, String ip, int provider) {
        hosts.put(domain, new Hosts.Value(ip, provider));
        return this;
    }

    /**
     * 插入指定运营商的hosts配置
     *
     * @param domain 域名
     * @param ip     ip
     * @return 当前的DnsManager，便于链式调用
     */
    public DnsManager putHosts(String domain, String ip) {
        hosts.put(domain, ip);
        return this;
    }

    private static class DummySorter implements IpSorter {
        private AtomicInteger pos = new AtomicInteger();

        @Override
        public String[] sort(String[] ips) {
            return ips;
        }
    }
}


package in.srain.cube.views;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import in.srain.cube.R;
import in.srain.cube.util.CLog;
import in.srain.cube.util.CubeDebug;

/**
 * @author http://www.liaohuqiu.net
 */
public class ScrollHeaderFrame extends RelativeLayout {

    private static final boolean DEBUG = CubeDebug.DEBUG_SCROLL_HEADER_FRAME;
    private static final String LOG_TAG = ScrollHeaderFrame.class.getName();

    private int mHeaderHeight;
    private int mCurrentPos = 0;
    private int mLastPos = 0;
    private int mHeaderId = 0;
    private int mContainerId = 0;
    private boolean mDisabled = false;

    private ViewGroup mContentViewContainer;
    private View mHeaderContainer;
    private long mLastTime;

    private PointF mPtLastMove = new PointF();
    private IScrollHeaderFrameHandler mIScrollHeaderFrameHandler;

    public ScrollHeaderFrame(Context context) {
        this(context, null);
    }

    public ScrollHeaderFrame(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScrollHeaderFrame(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.ScrollHeaderFrame, 0, 0);
        if (arr != null) {
            if (arr.hasValue(R.styleable.ScrollHeaderFrame_scrollheaderframe_header)) {
                mHeaderId = arr.getResourceId(R.styleable.ScrollHeaderFrame_scrollheaderframe_header, 0);
            }
            if (arr.hasValue(R.styleable.ScrollHeaderFrame_scrollheaderframe_conent_container)) {
                mContainerId = arr.getResourceId(R.styleable.ScrollHeaderFrame_scrollheaderframe_conent_container, 0);
            }
            if (arr.hasValue(R.styleable.ScrollHeaderFrame_scrollheaderframe_disable)) {
                mDisabled = arr.getBoolean(R.styleable.ScrollHeaderFrame_scrollheaderframe_disable, false);
            }
            arr.recycle();
        }
    }

    public View getContentView() {
        return mContentViewContainer;
    }

    public View getHeaderView() {
        return mHeaderContainer;
    }

    public void setHandler(IScrollHeaderFrameHandler handler) {
        mIScrollHeaderFrameHandler = handler;
        CLog.d(LOG_TAG, "setHandler: %s", this);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mHeaderContainer = findViewById(mHeaderId);
        mContentViewContainer = (ViewGroup) findViewById(mContainerId);
        setDrawingCacheEnabled(false);
        setBackgroundDrawable(null);
        setClipChildren(false);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (DEBUG) {
            Log.d(LOG_TAG, String.format("onLayout: current %s, %s %s %s %s", mCurrentPos, l, t, r, b));
        }

        int headerHeight = mHeaderContainer.getMeasuredHeight();
        if (headerHeight == 0) {
            return;
        }

        int w = getMeasuredWidth();
        int h = getMeasuredHeight();
        int pos = 0;

        mHeaderContainer.layout(0, pos, w, pos + headerHeight);
        mContentViewContainer.layout(0, pos + headerHeight, w, h + headerHeight);
    }

    /**
     * if deltaY > 0, tryToMove the content down
     */
    private boolean tryToMove(float deltaY) {

        // has reached the bottom
        if ((deltaY > 0 && mCurrentPos == 0)) {
            if (DEBUG) {
                Log.d(LOG_TAG, String.format("has reached the bottom"));
            }
            return false;
        }

        // has reached the top
        if ((deltaY < 0 && mCurrentPos == -mHeaderHeight)) {
            if (DEBUG) {
                Log.d(LOG_TAG, String.format("has reached the top"));
            }
            return false;
        }

        int to = mCurrentPos + (int) deltaY;

        // over top
        if (to < -mHeaderHeight) {
            if (DEBUG) {
                Log.d(LOG_TAG, String.format("over top"));
            }
            to = -mHeaderHeight;
        }

        // over bottom
        if (to > 0) {
            if (DEBUG) {
                Log.d(LOG_TAG, String.format("over bottom"));
            }
            to = 0;
        }
        return moveTo(to);
    }

    private boolean moveTo(int to) {
        if (DEBUG) {
            Log.d(LOG_TAG, String.format("moveTo: %s %s, %s", to, mCurrentPos, mHeaderHeight));
        }
        if (mCurrentPos == to) {
            return false;
        }
        int y = mCurrentPos - to;
        mCurrentPos = to;
        updatePos();
        return true;
    }

    private void updatePos() {
        int change = mCurrentPos - mLastPos;

        // mHeaderContainer.scrollTo(0, -change);
        // mContentViewContainer.scrollTo(0, -change);
        scrollTo(0, -change);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int w = getMeasuredWidth();
        int h = getMeasuredHeight();
        mHeaderHeight = mHeaderContainer.getMeasuredHeight();

        if (DEBUG) {
            Log.d(LOG_TAG, String.format("onMeasure %s getMeasuredHeight: %s", h, mHeaderContainer.getMeasuredHeight()));
        }

        mContentViewContainer.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        if (!isEnabled() || mDisabled) {
            return super.dispatchTouchEvent(e);
        }
        boolean handled = super.dispatchTouchEvent(e);
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_DOWN:
                mLastTime = e.getEventTime();
                mPtLastMove.set(e.getX(), e.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                if (mHeaderHeight == 0) {
                    break;
                }

                float deltaY = (int) (e.getY() - mPtLastMove.y);
                mPtLastMove.set(e.getX(), e.getY());

                float speed = Math.abs(deltaY / (mLastTime - e.getEventTime()));
                mLastTime = e.getEventTime();

                boolean moveUp = deltaY < 0;
                boolean canMoveUp = mCurrentPos > -mHeaderHeight;
                boolean moveDown = !moveUp;
                boolean canMoveDown = mCurrentPos < 0;
                if (DEBUG) {
                    Log.d(LOG_TAG, String.format("ACTION_MOVE: %s, speed: %s, moveUp: %s, canMoveUp: %s, moveDown: %s, canMoveDown: %s", speed, deltaY, moveUp, canMoveUp, moveDown, canMoveDown));
                }

                // disable move when header not reach top
                if (moveDown && mIScrollHeaderFrameHandler != null && !mIScrollHeaderFrameHandler.hasReachTop()) {
                    return handled;
                }

                if (speed >= 5 && moveDown && mIScrollHeaderFrameHandler != null) {
                    moveTo(0);
                    return handled;
                }
                if (speed >= 5 && moveUp && mIScrollHeaderFrameHandler != null) {
                    moveTo(-mHeaderHeight);
                    return handled;
                }

                if ((moveUp && canMoveUp) || (moveDown && canMoveDown)) {
                    tryToMove(deltaY);
                }
                break;
            default:
                break;
        }
        return handled;
    }
}


/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * 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 in.srain.cube.util;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
import android.util.Log;

/**
 * A wrapper for a broadcast receiver that provides network connectivity state information, independent of network type (mobile, Wi-Fi, etc.). {@hide}
 */
public class NetworkStatusManager {

    /**
     * 未知网络类别
     */
    public static final int NETWORK_CLASS_UNKNOWN = 0;
    public static final String NETWORK_CLASS_UNKNOWN_NAME = "UNKNOWN";
    /**
     * 2G网络
     */
    public static final int NETWORK_CLASS_2G = 1;
    public static final String NETWORK_CLASS_2G_NAME = "2G";
    /**
     * 3G网络
     */
    public static final int NETWORK_CLASS_3G = 2;
    public static final String NETWORK_CLASS_3G_NAME = "3G";
    /**
     * 4G网络
     */
    public static final int NETWORK_CLASS_4G = 3;
    public static final String NETWORK_CLASS_4G_NAME = "4G";
    /**
     * WIFI网络
     */
    public static final int NETWORK_CLASS_WIFI = 999;
    public static final String NETWORK_CLASS_WIFI_NAME = "WIFI";
    private static final String TAG = "NetworkStatusManager";
    private static final boolean DBG = true;
    private static NetworkStatusManager sInstance;
    private Context mContext;
    private State mState;
    private boolean mListening;
    private String mReason;
    private boolean mIsFailOver;
    private NetworkInfo mNetworkInfo;
    private boolean mIsWifi = false;
    /**
     * In case of a Disconnect, the connectivity manager may have already established, or may be attempting to establish, connectivity with another network. If so, {@code mOtherNetworkInfo} will be non-null.
     */
    private NetworkInfo mOtherNetworkInfo;
    private ConnectivityBroadcastReceiver mReceiver;

    private NetworkStatusManager() {
        mState = State.UNKNOWN;
        mReceiver = new ConnectivityBroadcastReceiver();
    }

    /**
     * {@link TelephonyManager#getNetworkClass}
     */
    private static int getMobileNetworkClass(int networkType) {
        switch (networkType) {
            case TelephonyManager.NETWORK_TYPE_GPRS:
            case TelephonyManager.NETWORK_TYPE_EDGE:
            case TelephonyManager.NETWORK_TYPE_CDMA:
            case TelephonyManager.NETWORK_TYPE_1xRTT:
            case TelephonyManager.NETWORK_TYPE_IDEN:
                return NETWORK_CLASS_2G;
            case TelephonyManager.NETWORK_TYPE_UMTS:
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
            case TelephonyManager.NETWORK_TYPE_HSDPA:
            case TelephonyManager.NETWORK_TYPE_HSUPA:
            case TelephonyManager.NETWORK_TYPE_HSPA:
            case TelephonyManager.NETWORK_TYPE_EVDO_B:
            case TelephonyManager.NETWORK_TYPE_EHRPD:
            case TelephonyManager.NETWORK_TYPE_HSPAP:
                return NETWORK_CLASS_3G;
            case TelephonyManager.NETWORK_TYPE_LTE:
                return NETWORK_CLASS_4G;
            default:
                return NETWORK_CLASS_UNKNOWN;
        }
    }

    public static void init(Context context) {
        sInstance = new NetworkStatusManager();
        sInstance.mIsWifi = checkIsWifi(context);
        sInstance.startListening(context);
    }

    public static NetworkStatusManager getInstance() {
        return sInstance;
    }

    public static boolean checkIsWifi(Context context) {
        ConnectivityManager connectivity = (ConnectivityManager) (context.getSystemService(Context.CONNECTIVITY_SERVICE));
        if (connectivity != null) {
            NetworkInfo networkInfo = connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (networkInfo != null && networkInfo.isConnectedOrConnecting()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 2G/3G/4G/WIFI
     *
     * @return
     */
    public int getNetworkType() {

        NetworkInfo activeNetworkInfo = getNetworkInfo();

        if (activeNetworkInfo != null) {
            // WIFI
            if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                return NETWORK_CLASS_WIFI;
            }
            // MOBILE
            else if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                return getMobileNetworkClass(activeNetworkInfo.getSubtype());
            }
        }

        return NETWORK_CLASS_UNKNOWN;
    }

    /**
     * This method starts listening for network connectivity state changes.
     *
     * @param context
     */
    public synchronized void startListening(Context context) {
        if (!mListening) {
            mContext = context;

            IntentFilter filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            context.registerReceiver(mReceiver, filter);
            mListening = true;
        }
    }

    /**
     * This method stops this class from listening for network changes.
     */
    public synchronized void stopListening() {
        if (mListening) {
            mContext.unregisterReceiver(mReceiver);
            mContext = null;
            mNetworkInfo = null;
            mOtherNetworkInfo = null;
            mIsFailOver = false;
            mReason = null;
            mListening = false;
        }
    }

    /**
     * Return the NetworkInfo associated with the most recent connectivity event.
     *
     * @return {@code NetworkInfo} for the network that had the most recent connectivity event.
     */
    public NetworkInfo getNetworkInfo() {
        return mNetworkInfo;
    }

    /**
     * If the most recent connectivity event was a DISCONNECT, return any information supplied in the broadcast about an alternate network that might be available. If this returns a non-null value, then another broadcast should follow shortly indicating whether connection to the other network succeeded.
     *
     * @return NetworkInfo
     */
    public NetworkInfo getOtherNetworkInfo() {
        return mOtherNetworkInfo;
    }

    /**
     * Returns true if the most recent event was for an attempt to switch over to a new network following loss of connectivity on another network.
     *
     * @return {@code true} if this was a fail over attempt, {@code false} otherwise.
     */
    public boolean isFailover() {
        return mIsFailOver;
    }

    /**
     * An optional reason for the connectivity state change may have been supplied. This returns it.
     *
     * @return the reason for the state change, if available, or {@code null} otherwise.
     */
    public String getReason() {
        return mReason;
    }

    public boolean isWifi() {
        return mIsWifi;
    }

    public String getNetworkTypeName() {
        switch (getNetworkType()) {
            case NETWORK_CLASS_WIFI:
                return NETWORK_CLASS_WIFI_NAME;
            case NETWORK_CLASS_2G:
                return NETWORK_CLASS_2G_NAME;
            case NETWORK_CLASS_3G:
                return NETWORK_CLASS_3G_NAME;
            case NETWORK_CLASS_4G:
                return NETWORK_CLASS_4G_NAME;
            case NETWORK_CLASS_UNKNOWN:
                return NETWORK_CLASS_UNKNOWN_NAME;
        }
        return NETWORK_CLASS_UNKNOWN_NAME;
    }

    public enum State {
        UNKNOWN,

        /**
         * This state is returned if there is connectivity to any network *
         */
        CONNECTED,
        /**
         * This state is returned if there is no connectivity to any network. This is set to true under two circumstances:
         *

         *
When connectivity is lost to one network, and there is no other available network to attempt to switch to.

         *
When connectivity is lost to one network, and the attempt to switch to another network fails.

         */
        NOT_CONNECTED
    }

    private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
        @SuppressWarnings("deprecation")
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || mListening == false) {
                Log.w(TAG, "onReceived() called with " + mState.toString() + " and " + intent);
                return;
            }

            boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);

            if (noConnectivity) {
                mState = State.NOT_CONNECTED;
            } else {
                mState = State.CONNECTED;
            }

            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
            mOtherNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);

            mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
            mIsFailOver = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
            if (DBG) {
                Log.d(TAG, "onReceive(): mNetworkInfo=" + mNetworkInfo + " mOtherNetworkInfo = " + (mOtherNetworkInfo == null ? "[none]" : mOtherNetworkInfo + " noConn=" + noConnectivity)
                        + " mState=" + mState.toString());
            }

            mIsWifi = checkIsWifi(mContext);
        }
    }
}


package in.srain.cube.request.sender;

import android.text.TextUtils;
import in.srain.cube.request.IRequest;
import in.srain.cube.request.RequestData;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MultiPartRequestSender extends PostRequestSender {

    private static final String LINE_FEED = "\r\n";
    private static final String CHARSET_DEFAULT = "UTF-8";
    private String mBoundary;
    private String mCharset = CHARSET_DEFAULT;

    private OutputStream mOutputStream;
    private PrintWriter mWriter;

    public MultiPartRequestSender(IRequest<?> request, HttpURLConnection httpURLConnection) {
        super(request, httpURLConnection);
    }

    @Override
    public void setup() throws IOException {
        super.setup();
        mBoundary = "===" + System.currentTimeMillis() + "===";
        mHttpURLConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + mBoundary);
        mOutputStream = mHttpURLConnection.getOutputStream();
        mWriter = new PrintWriter(new OutputStreamWriter(mOutputStream), true);
    }

    /**
     * post data to Server
     */
    public void send() throws IOException {

        HashMap<String, RequestData.UploadFileInfo> uploadFiles = mRequestData.getUploadFiles();

        HashMap<String, Object> mPostData = mRequestData.getPostData();
        // send data to Server
        if (mPostData != null && mPostData.size() != 0) {

            Iterator<Map.Entry<String, Object>> iterator = mPostData.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> item = iterator.next();
                Object value = item.getValue();
                if (value == null) {
                    value = "";
                }
                addFormField(item.getKey(), value.toString());
            }
        }

        // send file to server
        Iterator<Map.Entry<String, RequestData.UploadFileInfo>> iterator = uploadFiles.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, RequestData.UploadFileInfo> item = iterator.next();
            RequestData.UploadFileInfo uploadFileInfo = item.getValue();
            addFilePart(uploadFileInfo.fieldName, uploadFileInfo.uploadFile, uploadFileInfo.fileName);
        }
    }

    @Override
    public void getResponse(StringBuilder sb) throws IOException {
        mWriter.append(LINE_FEED).flush();
        mWriter.append("--" + mBoundary + "--").append(LINE_FEED);
        mWriter.close();
        super.getResponse(sb);
    }

    /**
     * Adds a form field to the request
     *
     * @param name  field name
     * @param value field value
     */
    public void addFormField(String name, String value) {
        mWriter.append("--" + mBoundary).append(LINE_FEED);
        mWriter.append("Content-Disposition: form-data; name=\"" + name + "\"")
                .append(LINE_FEED);
        mWriter.append("Content-Type: text/plain; charset=" + mCharset).append(
                LINE_FEED);
        mWriter.append(LINE_FEED);
        mWriter.append(value).append(LINE_FEED);
        mWriter.flush();
    }

    /**
     * Adds a upload file section to the request
     *
     * @param fieldName  name attribute in <input type="file" name="..." />
     * @param uploadFile a File to be uploaded
     * @param fileName   the filename field
     * @throws IOException
     */
    public void addFilePart(String fieldName, File uploadFile, String fileName) throws IOException {
        if (TextUtils.isEmpty(fileName)) {
            fileName = uploadFile.getName();
        }
        mWriter.append("--" + mBoundary).append(LINE_FEED);
        mWriter.append(
                "Content-Disposition: form-data; name=\"" + fieldName
                        + "\"; filename=\"" + fileName + "\"")
                .append(LINE_FEED);
        mWriter.append(
                "Content-Type: " + URLConnection.guessContentTypeFromName(fileName))
                .append(LINE_FEED);
        mWriter.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
        mWriter.append(LINE_FEED);
        mWriter.flush();

        FileInputStream inputStream = new FileInputStream(uploadFile);
        byte[] buffer = new byte[4096];
        int bytesRead = -1;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            mOutputStream.write(buffer, 0, bytesRead);
        }
        mOutputStream.flush();
        inputStream.close();

        mWriter.append(LINE_FEED);
        mWriter.flush();
    }
}


package in.srain.cube.image;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import in.srain.cube.image.drawable.RecyclingBitmapDrawable;
import in.srain.cube.image.iface.ImageMemoryCache;
import in.srain.cube.image.iface.ImageReSizer;
import in.srain.cube.util.CLog;
import in.srain.cube.util.CubeDebug;
import in.srain.cube.util.Version;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * This class handles disk and memory caching of bitmaps.
 *

 * Most of the code is taken from the Android best practice of displaying Bitmaps Displaying Bitmaps Efficiently.
 *
 * @author http://www.liaohuqiu.net
 */
public class ImageProvider {

    protected static final boolean DEBUG = CubeDebug.DEBUG_IMAGE;

    protected static final String TAG = CubeDebug.DEBUG_IMAGE_LOG_TAG_PROVIDER;

    private static final String MSG_FETCH_BEGIN = "%s fetchBitmapData";
    private static final String MSG_FETCH_BEGIN_IDENTITY_KEY = "%s identityKey: %s";
    private static final String MSG_FETCH_BEGIN_FILE_CACHE_KEY = "%s fileCacheKey: %s";
    private static final String MSG_FETCH_BEGIN_IDENTITY_URL = "%s identityUrl: %s";
    private static final String MSG_FETCH_BEGIN_ORIGIN_URL = "%s originUrl: %s";

    private static final String MSG_FETCH_TRY_REUSE = "%s Disk Cache not hit. Try to reuse";
    private static final String MSG_FETCH_HIT_DISK_CACHE = "%s Disk Cache hit";
    private static final String MSG_FETCH_REUSE_SUCCESS = "%s reuse size: %s";
    private static final String MSG_FETCH_REUSE_FAIL = "%s reuse fail: %s, %s";
    private static final String MSG_FETCH_DOWNLOAD = "%s downloading: %s";
    private static final String MSG_DECODE = "%s decode: %sx%s inSampleSize:%s";

    private ImageMemoryCache mMemoryCache;
    private ImageDiskCacheProvider mDiskCacheProvider;

    public ImageProvider(Context context, ImageMemoryCache memoryCache, ImageDiskCacheProvider fileProvider) {
        mMemoryCache = memoryCache;
        mDiskCacheProvider = fileProvider;
    }

    /**
     * Return the byte usage per pixel of a bitmap based on its configuration.
     *
     * @param config The bitmap configuration.
     * @return The byte usage per pixel.
     */
    private static int getBytesPerPixel(Config config) {
        if (config == Config.ARGB_8888) {
            return 4;
        } else if (config == Config.RGB_565) {
            return 2;
        } else if (config == Config.ARGB_4444) {
            return 2;
        } else if (config == Config.ALPHA_8) {
            return 1;
        }
        return 1;
    }

    /**
     * Get the size in bytes of a bitmap in a BitmapDrawable. Note that from Android 4.4 (KitKat) onward this returns the allocated memory size of the bitmap which can be larger than the actual bitmap data byte count (in the case it was re-used).
     *
     * @param value
     * @return size in bytes
     */
    @TargetApi(19) // @TargetApi(VERSION_CODES.KITKAT)
    public static long getBitmapSize(BitmapDrawable value) {
        if (null == value) {
            return 0;
        }
        Bitmap bitmap = value.getBitmap();

        // From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
        // larger than bitmap byte count.
        if (Version.hasKitKat()) {
            return bitmap.getAllocationByteCount();
        }

        if (Version.hasHoneycombMR1()) {
            return bitmap.getByteCount();
        }

        // Pre HC-MR1
        return bitmap.getRowBytes() * bitmap.getHeight();
    }

    /**
     * Create a BitmapDrawable which can be managed in ImageProvider
     *
     * @param resources
     * @param bitmap
     * @return
     */
    public BitmapDrawable createBitmapDrawable(Resources resources, Bitmap bitmap) {
        if (bitmap == null) {
            return null;
        }
        BitmapDrawable drawable = null;
        if (bitmap != null) {
            if (Version.hasHoneycomb()) {
                // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
                drawable = new BitmapDrawable(resources, bitmap);
            } else {
                // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
                // which will recycle automatically
                drawable = new RecyclingBitmapDrawable(resources, bitmap);
            }
        }
        return drawable;
    }

    /**
     * Get from memory cache.
     */
    public BitmapDrawable getBitmapFromMemCache(ImageTask imageTask) {
        BitmapDrawable memValue = null;

        if (mMemoryCache != null) {
            memValue = mMemoryCache.get(imageTask.getIdentityKey());
        }

        return memValue;
    }

    public void addBitmapToMemCache(String key, BitmapDrawable drawable) {

        // If the API level is lower than 11, do not use memory cache
        if (key == null || drawable == null || !Version.hasHoneycomb()) {
            return;
        }

        // Add to memory cache
        if (mMemoryCache != null) {
            mMemoryCache.set(key, drawable);
        }
    }

    public void cancelTask(ImageTask task) {
        mDiskCacheProvider.getDiskCache().abortEdit(task.getFileCacheKey());
    }

    /**
     * Get Bitmap. If not exist in file cache, will try to re-use the file cache of the other sizes.
     *

     * If no file cache can be used, download then save to file.
     */
    public Bitmap fetchBitmapData(ImageLoader imageLoader, ImageTask imageTask, ImageReSizer imageReSizer) {
        Bitmap bitmap = null;
        if (mDiskCacheProvider == null) {
            return null;
        }
        FileInputStream inputStream = null;

        String fileCacheKey = imageTask.getFileCacheKey();
        ImageReuseInfo reuseInfo = imageTask.getRequest().getImageReuseInfo();

        if (DEBUG) {
            Log.d(TAG, String.format(MSG_FETCH_BEGIN, imageTask));
            Log.d(TAG, String.format(MSG_FETCH_BEGIN_IDENTITY_KEY, imageTask, imageTask.getIdentityKey()));
            Log.d(TAG, String.format(MSG_FETCH_BEGIN_FILE_CACHE_KEY, imageTask, fileCacheKey));
            Log.d(TAG, String.format(MSG_FETCH_BEGIN_ORIGIN_URL, imageTask, imageTask.getOriginUrl()));
            Log.d(TAG, String.format(MSG_FETCH_BEGIN_IDENTITY_URL, imageTask, imageTask.getIdentityUrl()));
        }

        // read from file cache
        inputStream = mDiskCacheProvider.getInputStream(fileCacheKey);

        // try to reuse
        if (inputStream == null) {
            if (reuseInfo != null && reuseInfo.getReuseSizeList() != null && reuseInfo.getReuseSizeList().length > 0) {
                if (DEBUG) {
                    Log.d(TAG, String.format(MSG_FETCH_TRY_REUSE, imageTask));
                }

                final String[] sizeKeyList = reuseInfo.getReuseSizeList();
                for (int i = 0; i < sizeKeyList.length; i++) {
                    String size = sizeKeyList[i];
                    final String key = imageTask.generateFileCacheKeyForReuse(size);
                    inputStream = mDiskCacheProvider.getInputStream(key);

                    if (inputStream != null) {
                        if (DEBUG) {
                            Log.d(TAG, String.format(MSG_FETCH_REUSE_SUCCESS, imageTask, size));
                        }
                        break;
                    } else {
                        if (DEBUG) {
                            Log.d(TAG, String.format(MSG_FETCH_REUSE_FAIL, imageTask, size, key));
                        }
                    }
                }
            }
        } else {
            if (DEBUG) {
                Log.d(TAG, String.format(MSG_FETCH_HIT_DISK_CACHE, imageTask));
            }
        }

        if (imageTask.getStatistics() != null) {
            imageTask.getStatistics().s2_afterCheckFileCache(inputStream != null);
        }

        // We've got nothing from file cache
        if (inputStream == null) {
            String url = imageReSizer.getRemoteUrl(imageTask);
            if (DEBUG) {
                Log.d(TAG, String.format(MSG_FETCH_DOWNLOAD, imageTask, url));
            }
            inputStream = mDiskCacheProvider.downloadAndGetInputStream(imageLoader.getImageDownloader(), imageTask, fileCacheKey, url);
            if (imageTask.getStatistics() != null) {
                imageTask.getStatistics().s3_afterDownload();
            }
            if (inputStream == null) {
                imageTask.setError(ImageTask.ERROR_NETWORK);
                CLog.e(TAG, "%s download fail: %s %s", imageTask, fileCacheKey, url);
            }
        }
        if (inputStream != null) {
            try {
                bitmap = decodeSampledBitmapFromDescriptor(inputStream.getFD(), imageTask, imageReSizer);
                if (bitmap == null) {
                    imageTask.setError(ImageTask.ERROR_BAD_FORMAT);
                    CLog.e(TAG, "%s decode bitmap fail, bad format. %s, %s", imageTask, fileCacheKey, imageReSizer.getRemoteUrl(imageTask));
                }
            } catch (IOException e) {
                CLog.e(TAG, "%s decode bitmap fail, may be out of memory. %s, %s", imageTask, fileCacheKey, imageReSizer.getRemoteUrl(imageTask));
                if (CubeDebug.DEBUG_IMAGE) {
                    e.printStackTrace();
                }
            }
        } else {
            CLog.e(TAG, "%s fetch bitmap fail. %s, %s", imageTask, fileCacheKey, imageReSizer.getRemoteUrl(imageTask));
        }
        if (imageTask != null && imageTask.getStatistics() != null) {
            imageTask.getStatistics().s4_afterDecode(mDiskCacheProvider.getSize(fileCacheKey));
        }
        return bitmap;
    }

    private Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor, ImageTask imageTask, ImageReSizer imageReSizer) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        imageTask.setBitmapOriginSize(options.outWidth, options.outHeight);

        // Calculate inSampleSize
        options.inSampleSize = imageReSizer.getInSampleSize(imageTask);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        if (DEBUG) {
            Log.d(TAG, String.format(MSG_DECODE, imageTask, imageTask.getBitmapOriginSize().x, imageTask.getBitmapOriginSize().y, options.inSampleSize));
        }

        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        return bitmap;
    }

    private Bitmap decodeSampledBitmapFromInputStream(InputStream stream, ImageTask imageTask, ImageReSizer imageReSizer) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        // try to decode height and width from InputStream
        BitmapFactory.decodeStream(stream, null, options);

        imageTask.setBitmapOriginSize(options.outWidth, options.outHeight);

        // Calculate inSampleSize
        options.inSampleSize = imageReSizer.getInSampleSize(imageTask);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        if (DEBUG) {
            Log.d(TAG, String.format(MSG_DECODE, imageTask, imageTask.getBitmapOriginSize().x, imageTask.getBitmapOriginSize().y, options.inSampleSize));
        }

        Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

        return bitmap;
    }

    public void flushFileCache() {
        if (null != mDiskCacheProvider) {
            mDiskCacheProvider.flushDiskCacheAsync();
        }
    }

    /**
     * clear the memory cache
     */
    public void clearMemoryCache() {
        if (mMemoryCache != null) {
            mMemoryCache.clear();
        }
    }

    /**
     * clear the disk cache
     */
    public void clearDiskCache() {
        if (null != mDiskCacheProvider) {
            try {
                mDiskCacheProvider.getDiskCache().clear();
            } catch (IOException e) {
            }
        }
    }

    public long getMemoryCacheMaxSpace() {
        return mMemoryCache.getMaxSize();
    }

    public long getMemoryCacheUsedSpace() {
        return mMemoryCache.getUsedSpace();
    }

    /**
     * return the file cache path
     *
     * @return
     */
    public String getFileCachePath() {
        if (null != mDiskCacheProvider) {
            return mDiskCacheProvider.getDiskCache().getDirectory().getAbsolutePath();
        }
        return null;
    }

    /**
     * get the used space
     *
     * @return
     */
    public long getFileCacheUsedSpace() {
        return null != mDiskCacheProvider ? mDiskCacheProvider.getDiskCache().getSize() : 0;
    }

    public long getFileCacheMaxSpace() {
        if (null != mDiskCacheProvider) {
            return mDiskCacheProvider.getDiskCache().getCapacity();
        }
        return 0;
    }
}


package in.srain.cube.image;

import android.content.Context;
import android.text.TextUtils;
import in.srain.cube.cache.DiskFileUtils;
import in.srain.cube.image.iface.*;
import in.srain.cube.image.impl.*;

/**
 * Create an {@link ImageLoader}.
 * Here defines two {@link ImageLoader}, one is the default, the other one is the stable.
 * You can use the stable one to load the images which will not change frequently.
 *
 * @author http://www.liaohuqiu.net
 */
public class ImageLoaderFactory {

    public static int DEFAULT_FILE_CACHE_SIZE_IN_KB = 10 * 1024; // 10M;
    private static String DEFAULT_FILE_CACHE_DIR = "cube-image";
    private static String STABLE_FILE_CACHE_DIR = "cube-image-stable";

    private static ImageProvider sDefaultImageProvider;
    private static ImageProvider sStableImageProvider;

    private static ImageReSizer sDefaultImageReSizer;
    private static ImageTaskExecutor sDefaultImageTaskExecutor;
    private static ImageLoadHandler sDefaultImageLoadHandler;
    private static ImageMemoryCache sDefaultImageMemoryCache;
    private static ImageDownloader sImageDownloader;
    private static NameGenerator sNameGenerator;

    public static int getDefaultMemoryCacheSizeInKB() {
        float percent = 0.2f;
        int memoryCacheSizeInKB = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
        return memoryCacheSizeInKB;
    }

    /**
     * customize cache
     *
     * @param context
     * @param memoryCacheSizeInKB      How many memory should use. Will not be greater than 50% of free memory
     * @param defaultDiskCacheSizeInKB Default disk cache size.
     */
    public static void customizeCache(Context context, int memoryCacheSizeInKB, int defaultDiskCacheSizeInKB) {
        customizeCache(context, memoryCacheSizeInKB, defaultDiskCacheSizeInKB, 0);
    }

    /**
     * customize cache
     *
     * @param context
     * @param memoryCacheSizeInKB      How many memory should use. Will not be greater than 50% of free memory
     * @param defaultDiskCachePath     Default image cache path.
     *                                 Absolute path or a relative path under cache directory. External cache first.
     *                                 If not specified, using {@link #DEFAULT_FILE_CACHE_DIR} under cache directory.
     * @param defaultDiskCacheSizeInKB Default disk cache size.
     */
    public static void customizeCache(Context context, int memoryCacheSizeInKB, String defaultDiskCachePath, int defaultDiskCacheSizeInKB) {
        customizeCache(context, memoryCacheSizeInKB, defaultDiskCachePath, defaultDiskCacheSizeInKB, null, 0);
    }

    /**
     * customize cache
     *
     * @param context
     * @param memoryCacheSizeInKB      How many memory should use. Will not be greater than 50% of free memory
     * @param defaultDiskCacheSizeInKB Default disk cache size.
     * @param stableDiskCacheSizeInKB  Stable disk cache size.
     */
    public static void customizeCache(Context context, int memoryCacheSizeInKB, int defaultDiskCacheSizeInKB, int stableDiskCacheSizeInKB) {
        customizeCache(context, memoryCacheSizeInKB, null, defaultDiskCacheSizeInKB, null, stableDiskCacheSizeInKB);
    }

    /**
     * customize cache
     *
     * @param context
     * @param memoryCacheSizeInKB      How many memory should use. Will not be greater than 50% of free memory
     * @param defaultDiskCachePath     Default image cache path.
     *                                 Absolute path or a relative path under cache directory. External cache first.
     *                                 If not specified, using {@link #DEFAULT_FILE_CACHE_DIR} under cache directory.
     * @param defaultDiskCacheSizeInKB Default disk cache size.
     * @param stableDiskCachePath      Path for stable cache directory. Default is {@link #STABLE_FILE_CACHE_DIR}
     * @param stableDiskCacheSizeInKB  Stable disk cache size.
     */
    public static void customizeCache(Context context, int memoryCacheSizeInKB,
                                      String defaultDiskCachePath, int defaultDiskCacheSizeInKB,
                                      String stableDiskCachePath, int stableDiskCacheSizeInKB) {

        // init memory cache first
        if (memoryCacheSizeInKB > 0) {
            int maxCacheSizeInKB = Math.round(0.5f * Runtime.getRuntime().maxMemory() / 1024);
            memoryCacheSizeInKB = Math.min(memoryCacheSizeInKB, maxCacheSizeInKB);
            sDefaultImageMemoryCache = new DefaultMemoryCache(memoryCacheSizeInKB);
        }

        if (defaultDiskCacheSizeInKB > 0 && !TextUtils.isEmpty(defaultDiskCachePath)) {
            ImageDiskCacheProvider imageFileProvider = getImageFileProvider(context, defaultDiskCachePath, defaultDiskCacheSizeInKB, DEFAULT_FILE_CACHE_DIR);
            if (imageFileProvider != null) {
                sDefaultImageProvider = new ImageProvider(context, getDefaultImageMemoryCache(), imageFileProvider);
            }
        }

        if (stableDiskCacheSizeInKB > 0 && !TextUtils.isEmpty(stableDiskCachePath)) {
            ImageDiskCacheProvider imageFileProvider = getImageFileProvider(context, stableDiskCachePath, stableDiskCacheSizeInKB, STABLE_FILE_CACHE_DIR);
            if (imageFileProvider != null) {
                sStableImageProvider = new ImageProvider(context, getDefaultImageMemoryCache(), imageFileProvider);
            }
        }
    }

    private static ImageDiskCacheProvider getImageFileProvider(Context context, String path, int sizeInKB, String fallbackCachePath) {
        if (sizeInKB <= 0) {
            sizeInKB = DEFAULT_FILE_CACHE_SIZE_IN_KB;
        }

        DiskFileUtils.CacheDirInfo dirInfo = DiskFileUtils.getDiskCacheDir(context, path, sizeInKB, fallbackCachePath);

        ImageDiskCacheProvider imageFileProvider = ImageDiskCacheProvider.createLru(dirInfo.realSize, dirInfo.path);

        if (imageFileProvider != null) {
            imageFileProvider.openDiskCacheAsync();
        }
        return imageFileProvider;
    }

    public static ImageLoader createStableImageLoader(Context context) {
        return createInner(context, getStableImageProvider(context), sDefaultImageLoadHandler);
    }

    public static ImageLoader createStableImageLoader(Context context, ImageLoadHandler imageLoadHandler) {
        return createInner(context, getStableImageProvider(context), imageLoadHandler);
    }

    public static ImageLoader create(Context context) {
        return createInner(context, getDefaultImageProvider(context), sDefaultImageLoadHandler);
    }

    public static ImageLoader create(Context context, ImageLoadHandler imageLoadHandler) {
        return createInner(context, getDefaultImageProvider(context), imageLoadHandler);
    }

    private static ImageLoader createInner(Context context, ImageProvider imageProvider, ImageLoadHandler imageLoadHandler) {
        return create(context, imageProvider, sDefaultImageTaskExecutor, sDefaultImageReSizer, imageLoadHandler);
    }

    private static ImageLoader create(Context context, ImageProvider imageProvider, ImageTaskExecutor imageTaskExecutor, ImageReSizer imageReSizer, ImageLoadHandler imageLoadHandler) {

        if (imageProvider == null) {
            imageProvider = getDefaultImageProvider(context);
        }

        if (imageTaskExecutor == null) {
            imageTaskExecutor = DefaultImageTaskExecutor.getInstance();
        }

        if (imageReSizer == null) {
            imageReSizer = DefaultImageReSizer.getInstance();
        }

        if (imageLoadHandler == null) {
            imageLoadHandler = new DefaultImageLoadHandler(context);
        }
        ImageLoader imageLoader = new ImageLoader(context, imageProvider, imageTaskExecutor, imageReSizer, imageLoadHandler);

        if (sImageDownloader != null) {
            imageLoader.setImageDownloader(sImageDownloader);
        }
        return imageLoader;
    }

    /**
     * set a default {@link ImageDownloader} for all {@link ImageLoader}
     *
     * @param imageDownloader
     */
    @SuppressWarnings({"unused"})
    public static void setDefaultImageDownloader(ImageDownloader imageDownloader) {
        sImageDownloader = imageDownloader;
    }

    public static void setDefaultImageReSizer(ImageReSizer imageReSizer) {
        sDefaultImageReSizer = imageReSizer;
    }

    public static void setDefaultImageTaskExecutor(ImageTaskExecutor imageTaskExecutor) {
        sDefaultImageTaskExecutor = imageTaskExecutor;
    }

    public static void setDefaultImageLoadHandler(ImageLoadHandler imageLoadHandler) {
        sDefaultImageLoadHandler = imageLoadHandler;
    }

    public static void setDefaultImageProvider(ImageProvider imageProvider) {
        sDefaultImageProvider = imageProvider;
    }

    public static void setStableImageProvider(ImageProvider imageProvider) {
        sStableImageProvider = imageProvider;
    }

    public static ImageProvider getDefaultImageProvider(Context context) {
        if (null == sDefaultImageProvider) {
            ImageDiskCacheProvider imageFileProvider = getImageFileProvider(context, null, 0, DEFAULT_FILE_CACHE_DIR);
            sDefaultImageProvider = new ImageProvider(context, getDefaultImageMemoryCache(), imageFileProvider);
        }
        return sDefaultImageProvider;
    }

    public static ImageProvider getStableImageProvider(Context context) {
        if (null == sStableImageProvider) {
            ImageDiskCacheProvider imageFileProvider = getImageFileProvider(context, null, 0, STABLE_FILE_CACHE_DIR);
            sStableImageProvider = new ImageProvider(context, getDefaultImageMemoryCache(), imageFileProvider);
        }
        return sStableImageProvider;
    }

    private static ImageMemoryCache getDefaultImageMemoryCache() {
        if (sDefaultImageMemoryCache == null) {
            sDefaultImageMemoryCache = new DefaultMemoryCache(getDefaultMemoryCacheSizeInKB());
        }
        return sDefaultImageMemoryCache;
    }

    public static void setNameGenerator(NameGenerator nameGenerator) {
        sNameGenerator = nameGenerator;
    }

    public static NameGenerator getNameGenerator() {
        if (sNameGenerator == null) {
            return DefaultNameGenerator.getInstance();
        }
        return sNameGenerator;
    }
}