Android Open Source - PkRequestManager Pk Request Manager






From Project

Back to project page PkRequestManager.

License

The source code is released under:

MIT License

If you think the Android project PkRequestManager listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * The MIT License (MIT)// w  w  w. j  av a2  s. c  om
 * 
 * Copyright (c) 2014 Pkmmte Xeleon
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.pkmmte.requestmanager;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;


public class PkRequestManager extends Static
{
  // General Public Constants
  public static final CompressFormat PNG = CompressFormat.PNG;
  public static final CompressFormat JPEG = CompressFormat.JPEG;
  public static final CompressFormat WEBP = CompressFormat.WEBP;
  public static final String PLAY_LINK_BASE = "https://play.google.com/store/apps/details?id=";
  public static final int MAX_PROGRESS = 100;
  public static final int STATUS_PRELOAD = 0;
  public static final int STATUS_LOADING_INSTALLED = 1;
  public static final int STATUS_LOADING_APPFILTER = 2;
  public static final int STATUS_FILTERING = 3;
  public static final int STATUS_LOADED = 4;
  
  // Keep a single instance throughout the app for simplicity
  private static PkRequestManager mInstance = null;
  
  // For issue tracking purposes
  private boolean debugEnabled;
  private static final String LOG_TAG = "RequestManager";
  
  // Custom request configuration data
  private RequestSettings mSettings;
  
  // Context is always useful for some reason.
  private Context mContext;
  
  // Keep an activity instance for those moments you really need it
  private Activity mActivity;
  
  // Keep track of how many requests were sent this session
  private int numRequestsSent;
  
  // List of installed app info and already defined apps as well as filtered apps
  private List<AppInfo> mApps;
  private List<AppInfo> mInstalledApps;
  private List<String>  mDefinedApps;
  
  // Background threads
  private AsyncTask<Void, Void, Void> loadTask;
  private AsyncTask<Void, Void, Void> sendTask;
  private AsyncTask<Void, Void, Void> autoTask;
  
  // Listeners for various loading events
  private List<InstalledAppLoadListener> mInstalledAppLoadListeners;
  private List<AppFilterListener> mAppFilterListeners;
  private List<AppLoadListener> mAppLoadListeners;
  private List<SendRequestListener> mSendRequestListeners;
  
  /**
   * Creates a global RequestManager instance.
   * 
   * @param context
   */
  public static void createInstance(Context context)
  {
    if (mInstance == null)
      mInstance = new PkRequestManager(context.getApplicationContext());
  }
  
  /**
   * Returns the global instance of this RequestManager.
   * If you don't remember whether or not you already created 
   * a previous instance, add the context as a parameter. 
   * 
   * @return
   */
  public static PkRequestManager getInstance()
  {
    return mInstance;
  }
  
  /**
   * Returns the global instance of this RequestManager.
   * 
   * @param context
   * @return
   */
  public static PkRequestManager getInstance(Context context)
  {
    if (mInstance == null)
      mInstance = new PkRequestManager(context.getApplicationContext());
    
    return mInstance;
  }
  
  /**
   * Standard RequestManager constructor.
   * 
   * @param context
   */
  public PkRequestManager(Context context)
  {
    this.debugEnabled = false;
    this.mSettings = new RequestSettings();
    this.mContext = context;
    this.numRequestsSent = 0;
    this.mApps = new ArrayList<AppInfo>();
    this.mInstalledApps = new ArrayList<AppInfo>();
    this.mDefinedApps = new ArrayList<String>();
    this.mInstalledAppLoadListeners = new ArrayList<InstalledAppLoadListener>();
    this.mAppFilterListeners = new ArrayList<AppFilterListener>();
    this.mAppLoadListeners = new ArrayList<AppLoadListener>();
    this.mSendRequestListeners = new ArrayList<SendRequestListener>();
    this.initLoadingTask();
    this.initSendTask();
    this.initAutomaticTask();
  }
  
  /**
   * Loads all apps installed on the device. This will also filter those 
   * already in the appfilter if you have that enabled in settings. 
   * All progress is reported via listeners.
   * <p>
   * This can take a while to process so make sure you're running it on 
   * a background thread. You can also call the "loadAppsAsync()" method.
   */
  public void loadApps()
  {
    // Loads already apps installed on device
    loadInstalledAppInfo();
    
    // If enabled, also load apps in the appfilter and... filter them
    if(mSettings.getFilterDefined()) {
      loadDefinedAppInfo();
      filterAppInfo();
    }
  }
  
  /**
   * Exactly like "loadApps()" but runs in it's own parallel background thread.
   * <p>
   * Loads all apps installed on the device asynchronously. This will also filter 
   * those already in the appfilter if you have that enabled in settings. 
   * All progress is reported via listeners.
   */
  public void loadAppsAsync()
  {
    loadAppsAsync(true);
  }
  
  /**
   * Exactly like "loadApps()" but runs in it's own background thread.
   * <p>
   * Loads all apps installed on the device asynchronously. This will also filter 
   * those already in the appfilter if you have that enabled in settings. 
   * All progress is reported via listeners.
   * 
   * @param parallel  Boolean indicating whether to run serially or in parallel. 
   *           True for parallel, False for serial.
   */
  public void loadAppsAsync(boolean parallel)
  {
    if(loadTask.getStatus() == AsyncTask.Status.PENDING) {
      // Execute task if it's ready to go!
      loadTask.executeOnExecutor(parallel ? AsyncTask.THREAD_POOL_EXECUTOR : AsyncTask.SERIAL_EXECUTOR);
    }
    else if(loadTask.getStatus() == AsyncTask.Status.RUNNING && debugEnabled) {
      // Don't execute if already running
      Log.d(LOG_TAG, "Task is already running...");
    }
    else if(loadTask.getStatus() == AsyncTask.Status.FINISHED) {
      // Okay, this is not supposed to happen. Reset and recall.
      if(debugEnabled)
        Log.d(LOG_TAG, "Uh oh, it appears the task has finished without being reset. Resetting task...");
      
      initLoadingTask();
      loadAppsAsync(parallel);
    }
  }
  
  /**
   * Loads all apps if they have not yet been loaded. 
   * This method keeps in mind your current settings.
   */
  public void loadAppsIfEmpty()
  {
    if(!appsLoaded())
      loadApps();
  }
  

  /**
   * Exactly like "loadAppsIfEmpty()" but runs in it's own background thread.
   * <p>
   * Loads all apps if they have not yet been loaded. 
   * This method keeps in mind your current settings.
   */
  public void loadAppsIfEmptyAsync()
  {
    if(!appsLoaded())
      loadAppsAsync();
  }
  
  /**
   * Sends a request via email. This will return prematurely if no emails have 
   * been set in your settings or if no apps were selected. Data sent depends on 
   * your settings. All progress is reported via listeners.
   * <p>
   * Note: The send intent won't run immediately on some devices. As a hacky workaround, 
   * make sure to "setActivity()" right before running this. The listener also passes the 
   * send intent along with all the data attached to it. Manually launch the intent if you 
   * prefer not to attach the activity.
   */
  public void sendRequest()
  {
    sendRequest(false, false);
  }
  
  /**
   * Sends a request via email. This will return prematurely if no emails have 
   * been set in your settings or if no apps were selected. Data sent depends on 
   * your settings. All progress is reported via listeners.
   * <p>
   * Do not pass any parameters unless you know what you're doing! 
   * <p>
   * Note: The send intent won't run immediately on some devices. As a hacky workaround, 
   * make sure to "setActivity()" right before running this. The listener also passes the 
   * send intent along with all the data attached to it. Manually launch the intent if you 
   * prefer not to attach the activity.
   * 
   * @param selectAll
   * @param automatic
   */
  public void sendRequest(boolean selectAll, boolean automatic)
  {
    if(debugEnabled)
      Log.d(LOG_TAG, "Sending request...");
    
    // Retrieve proper apps
    final List<AppInfo> mAppList = automatic ? getAutomaticApps() : getApps();
    
    // Store used settings into local variables for slightly better performance
    final String[] emailAddresses = mSettings.getEmailAddresses();
    final String emailSubject = mSettings.getEmailSubject();
    final String emailPrecontent = mSettings.getEmailPrecontent();
    final String saveLoc = mSettings.getSaveLocation();
    final String saveLoc2 = mSettings.getSaveLocation2();
    final CompressFormat compressFormat = mSettings.getCompressFormat();
    final boolean appendInformation = mSettings.getAppendInformation();
    final boolean createAppfilter = mSettings.getCreateAppfilter();
    final boolean createZip = mSettings.getCreateZip();
    final int compressQuality = mSettings.getCompressQuality();
    
    // Check to see if data being set is valid.
    if(mAppList == null || mAppList.size() == 0) {
      if(debugEnabled)
        Log.d(LOG_TAG, "App List is either null or empty! Canceling send request...");
      return;
    }
    
    // Check to see if email is set, return if not. No point in doing work without anywhere to send it.
    if(emailAddresses == null) {
      if(debugEnabled)
        Log.d(LOG_TAG, "No proper email addresses were set! Canceling send request...");
      return;
    }
    
    // Loop through all listeners notifying them
        for(SendRequestListener mListener : mSendRequestListeners) {
          mListener.onRequestStart(automatic);
        }
    
    // Create file instances based on save locations
    File saveLocation = new File(saveLoc);
    File saveLocation2 = new File(saveLoc2);
    
    // Delete previous zips and files, if any.
    deleteDirectory(saveLocation);
    deleteDirectory(saveLocation2);
    
    // Recreate directories
    saveLocation.mkdirs();
    saveLocation2.mkdirs();
    
    // Initialize email builder and xml builder (For generating optional appfilter.xml values)
    StringBuilder emailBuilder = new StringBuilder();
    StringBuilder xmlBuilder = new StringBuilder();
    
    // Append email precontent (if enabled)
    if(debugEnabled)
      Log.d(LOG_TAG, "Appending email precontent" + (appendInformation ? " and device information" : ""));
    
    emailBuilder.append(emailPrecontent);
    
    // Loop through all selected app packages
    if(debugEnabled)
      Log.d(LOG_TAG, "Generating data...");
    final int numApps = mAppList.size();
    int numSelected = 0;
    int progress = 0;
    for(AppInfo mAppInfo : mAppList) {
      // Loop through all listeners notifying them
      progress = (int) (mAppList.indexOf(mAppInfo) * MAX_PROGRESS) / numApps;
          for(SendRequestListener mListener : mSendRequestListeners) {
            mListener.onRequestBuild(automatic, progress);
          }
      
      // Only deal with selected apps
      if(selectAll || mAppInfo.isSelected()) {
        // Build email content
        emailBuilder.append("Name: " + mAppInfo.getName() + "\n");
        emailBuilder.append("Code: " + mAppInfo.getCode() + "\n");
        emailBuilder.append("Link: " + PLAY_LINK_BASE + mAppInfo.getCode().split("/")[0] + "\n");
        emailBuilder.append("\n\n");
        
        // Generate appfilter content (if enabled)
        if(createAppfilter) {
          xmlBuilder.append("<!-- " + mAppInfo.getName() + " -->\n");
          xmlBuilder.append("<item component=\"ComponentInfo{" + mAppInfo.getCode() + "}\" drawable=\"" + convertDrawableName(mAppInfo.getName()) + "\"/>" + "\n");      
        }
        
        // Save drawable if we're going to compress into a zip later
        if(createZip) {
          // Attempt to use highest density drawable available
          Drawable appImage = getHighResDrawable(mAppInfo);
          
          // Fall back to regular drawable if unable to retrieve higher res
          if(appImage == null)
            appImage = mAppInfo.getImage();
          
          // Convert drawable into a bitmap before saving it
          Bitmap bitmap = drawableToBitmap(appImage);
          
          // Write bitmap to storage as PNG
          try {
            String bmDir = saveLoc2 + "/" + mAppInfo.getCode().split("/")[0] + "_" + mAppInfo.getCode().split("/")[1]+ ".png";
            new File(bmDir).getParentFile().mkdirs();
            FileOutputStream fOut = new FileOutputStream(bmDir);
            bitmap.compress(compressFormat, compressQuality, fOut);
            fOut.flush();
            fOut.close();
          }
          catch (FileNotFoundException e) {
            if(debugEnabled) {
              Log.e(LOG_TAG, "FileNotFoundException! Make sure the file isn't open in another app and you have writing permissions in your manifest.");
              e.printStackTrace();
            }
          }
          catch (IOException e) {
            if(debugEnabled) {
              Log.e(LOG_TAG, "IOException! Make sure you have the appropriate permissions.");
              e.printStackTrace();
            }
          }
        }
        
        // Increase our counter for number of apps selected
        numSelected++;
      }
    }
    
    // Append device information (if enabled)
    if(appendInformation) {
      emailBuilder.append("\nOS Version: " + System.getProperty("os.version") + "(" + Build.VERSION.INCREMENTAL + ")");
      emailBuilder.append("\nOS API Level: " + Build.VERSION.SDK_INT);
      emailBuilder.append("\nDevice: " + Build.DEVICE);
      emailBuilder.append("\nManufacturer: " + Build.MANUFACTURER);
      emailBuilder.append("\nModel (and Product): " + Build.MODEL + " (" + Build.PRODUCT + ")");
      
      try {
        PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
        emailBuilder.append("\nApp Version Name: " + appInfo.versionName);
        emailBuilder.append("\nApp Version Code: " + appInfo.versionCode);
      }
      catch(Exception e) {
        if(debugEnabled)
          Log.d(LOG_TAG, "Unable to append app version name/code...");
      }
    }
    
    // Return if no apps were selected
    if(numSelected == 0) {
      if(debugEnabled)
        Log.d(LOG_TAG, "No apps were selected. Can't send request data without any data!");
      return;
    }
    else if(debugEnabled)
        Log.d(LOG_TAG, "Successfully collected data for " + numSelected + " apps!");
    
    // Write appfilter.xml to storage, if enabled
    if(createAppfilter) {
      if(debugEnabled)
        Log.d(LOG_TAG, "Writing appfilter.xml...");
      try {
        new File(saveLoc2 + "/appfilter.xml").getParentFile().mkdirs();
        FileWriter fstream = new FileWriter(saveLoc2 + "/appfilter.xml");
        BufferedWriter out = new BufferedWriter(fstream);
        out.write(xmlBuilder.toString());
        out.close();
        
        if(debugEnabled)
          Log.d(LOG_TAG, "Succesfully wrote appfilter.xml to " + saveLoc2 + "/appfilter.xml!");
      }
      catch (Exception e) {
        if(debugEnabled) {
          Log.d(LOG_TAG, "Error writing generated appfilter.xml! Make sure you have writing permissions in your AndroidManifest.xml");
          e.printStackTrace();
        }
      }
    }
    
    // Create a zip file with the current date, if enabled
    String zipName = "mZip";
    if(createZip) {
      if(debugEnabled)
        Log.d(LOG_TAG, "Zipping files...");
      SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_hhmmss", Locale.getDefault());
      zipName = date.format(new Date());
      createZipFile(saveLoc2, true, saveLoc + "/" + zipName + ".zip");
    }
    
    // Initialize send intent with proper address, subject, and body values
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.putExtra(Intent.EXTRA_EMAIL, emailAddresses);
    intent.putExtra(Intent.EXTRA_SUBJECT, emailSubject);
    intent.putExtra(Intent.EXTRA_TEXT, emailBuilder.toString());
    
    // Attach zip/appfilter.xml if enabled
    if(createZip) {
      intent.setType("application/zip");
      final Uri uri = Uri.parse("file://" + saveLoc + "/" + zipName + ".zip");
      intent.putExtra(Intent.EXTRA_STREAM, uri);
    }
    else if(createAppfilter) {
      intent.setType("text/plain");
      final Uri uri = Uri.parse("file://" + saveLoc2 + "/appfilter.xml");
      intent.putExtra(Intent.EXTRA_STREAM, uri);
    }
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
    
    if(debugEnabled)
      Log.d(LOG_TAG, "Successfully collected data!");
    // Start the intent!
    boolean intentSuccessful;
    try {
      mActivity.startActivity(Intent.createChooser(intent, "Send Email"));
      intentSuccessful = true;
    }
    catch (ActivityNotFoundException localActivityNotFoundException) {
      if(debugEnabled)
        Log.d(LOG_TAG, "No email app has been found!");
      intentSuccessful = false;
    }
    catch (Exception e) {
      if(debugEnabled) {
        Log.d(LOG_TAG, "There was an error starting the intent!");
        e.printStackTrace();
      }
      intentSuccessful = false;
    }
    
    // Increase the number of sent requests, even if it failed
    numRequestsSent++;
    
    // Loop through all listeners notifying them
    for(SendRequestListener mListener : mSendRequestListeners) {
      mListener.onRequestFinished(automatic, intentSuccessful, intent);
    }
  }
  
  /**
   * Exactly like "sendRequest()" but runs in it's own parallel background thread.
   * <p>
   * Sends a request via email. This will return prematurely if no emails have 
   * been set in your settings or if no apps were selected. Data sent depends on 
   * your settings. All progress is reported via listeners.
   * <p>
   * Note: The send intent won't run immediately on some devices. As a hacky workaround, 
   * make sure to "setActivity()" right before running this. The listener also passes the 
   * send intent along with all the data attached to it. Manually launch the intent if you 
   * prefer not to attach the activity.
   */
  public void sendRequestAsync()
  {
    sendRequestAsync(true);
  }
  
  /**
   * Exactly like "sendRequest()" but runs in it's own background thread.
   * <p>
   * Sends a request via email. This will return prematurely if no emails have 
   * been set in your settings or if no apps were selected. Data sent depends on 
   * your settings. All progress is reported via listeners.
   * <p>
   * Note: The send intent won't run immediately on some devices. As a hacky workaround, 
   * make sure to "setActivity()" right before running this. The listener also passes the 
   * send intent along with all the data attached to it. Manually launch the intent if you 
   * prefer not to attach the activity.
   * 
   * @param parallel  Boolean indicating whether to run serially or in parallel. 
   *           True for parallel, False for serial.
   */
  public void sendRequestAsync(boolean parallel)
  {
    if(sendTask.getStatus() == AsyncTask.Status.PENDING) {
      // Execute task if it's ready to go!
      sendTask.executeOnExecutor(parallel ? AsyncTask.THREAD_POOL_EXECUTOR : AsyncTask.SERIAL_EXECUTOR);
    }
    else if(sendTask.getStatus() == AsyncTask.Status.RUNNING && debugEnabled) {
      // Don't execute if already running
      Log.d(LOG_TAG, "Task is already running...");
    }
    else if(sendTask.getStatus() == AsyncTask.Status.FINISHED) {
      // Okay, this is not supposed to happen. Reset and recall.
      if(debugEnabled)
        Log.d(LOG_TAG, "Uh oh, it appears the task has finished without being reset. Resetting task...");
      
      initSendTask();
      sendRequestAsync(parallel);
    }
  }
  
  /**
   * Automatically loads all app information (if not already) and sends it. 
   * Data being sent depends on your settings.
   * <p>
   * Warning: Do not run this on the main UI thread or you might get an ANR 
   * (Application Not Responding)! Use a background thread or call "sendAutomaticRequestAsync()" instead
   */
  public void sendAutomaticRequest()
  {
    // Load list of apps, if not already loaded
    if(mInstalledApps.size() == 0 || (mSettings.getFilterAutomatic() && (mApps.size() == 0 || mDefinedApps.size() == 0))) {
      loadInstalledAppInfo();
      
      if(mSettings.getFilterAutomatic()) {
        loadDefinedAppInfo();
        filterAppInfo();
      }
    }
    
    // Send request, marking all apps as selected
    sendRequest(true, true);
  }
  
  /**
   * Exactly like "sendAutomaticRequest()" but runs on it's own parallel background thread.
   * <p>
   * Automatically loads all app information (if not already) and sends it. 
   * Data being sent depends on your settings.
   */
  public void sendAutomaticRequestAsync()
  {
    sendAutomaticRequestAsync(true);
  }
  
  /**
   * Exactly like "sendAutomaticRequest()" but runs on it's own background thread.
   * <p>
   * Automatically loads all app information (if not already) and sends it. 
   * Data being sent depends on your settings.
   * 
   * @param parallel  Boolean indicating whether to run serially or in parallel. 
   *           True for parallel, False for serial.
   */
  public void sendAutomaticRequestAsync(boolean parallel)
  {
    if(autoTask.getStatus() == AsyncTask.Status.PENDING) {
      // Execute task if it's ready to go!
      autoTask.executeOnExecutor(parallel ? AsyncTask.THREAD_POOL_EXECUTOR : AsyncTask.SERIAL_EXECUTOR);
    }
    else if(autoTask.getStatus() == AsyncTask.Status.RUNNING && debugEnabled) {
      // Don't execute if already running
      Log.d(LOG_TAG, "Task is already running...");
    }
    else if(autoTask.getStatus() == AsyncTask.Status.FINISHED) {
      // Okay, this is not supposed to happen. Reset and recall.
      if(debugEnabled)
        Log.d(LOG_TAG, "Uh oh, it appears the task has finished without being reset. Resetting task...");
      
      initAutomaticTask();
      sendAutomaticRequestAsync(parallel);
    }
  }
  
  /**
   * Adds an InstalledAppLoadListener to this global instance.
   * 
   * @param listener
   */
  public void addInstalledAppLoadListener(InstalledAppLoadListener listener)
  {
    mInstalledAppLoadListeners.add(listener);
  }
  
  /**
   * Removes an InstalledAppLoadListener from this global instance.
   * 
   * @param listener
   */
  public void removeInstalledAppLoadListener(InstalledAppLoadListener listener)
  {
    mInstalledAppLoadListeners.remove(listener);
  }
  
  /**
   * Removes all InstalledAppLoadListeners from this global instance.
   */
  public void removeAllInstalledAppLoadListeners()
  {
    mInstalledAppLoadListeners.clear();
  }
  
  /**
   * Adds an AppFilterListener to this global instance.
   * 
   * @param listener
   */
  public void addAppFilterListener(AppFilterListener listener)
  {
    mAppFilterListeners.add(listener);
  }
  
  /**
   * Removes an AppFilterListener from this global instance.
   * 
   * @param listener
   */
  public void removeAppFilterListener(AppFilterListener listener)
  {
    mAppFilterListeners.remove(listener);
  }
  
  /**
   * Removes all AppFilterListeners from this global instance.
   */
  public void removeAllAppFilterListeners()
  {
    mAppFilterListeners.clear();
  }
  
  /**
   * Adds an AppLoadListener to this global instance.
   * 
   * @param listener
   */
  public void addAppLoadListener(AppLoadListener listener)
  {
    mAppLoadListeners.add(listener);
  }
  
  /**
   * Removes an AppLoadListener from this global instance.
   * 
   * @param listener
   */
  public void removeAppLoadListener(AppLoadListener listener)
  {
    mAppLoadListeners.remove(listener);
  }
  
  /**
   * Removes all AppFilterListeners from this global instance.
   */
  public void removeAllAppInfoLoadListeners()
  {
    mAppLoadListeners.clear();
  }
  
  /**
   * Adds an SendRequestListener to this global instance.
   * 
   * @param listener
   */
  public void addSendRequestListener(SendRequestListener listener)
  {
    mSendRequestListeners.add(listener);
  }
  
  /**
   * Removes an SendRequestListener from this global instance.
   * 
   * @param listener
   */
  public void removeSendRequestListener(SendRequestListener listener)
  {
    mSendRequestListeners.remove(listener);
  }
  
  /**
   * Removes all SendRequestListeners from this global instance.
   */
  public void removeAllSendRequestListeners()
  {
    mSendRequestListeners.clear();
  }
  
  /**
   * Removes all listeners from this global instance.
   */
  public void removeAllListeners()
  {
    mInstalledAppLoadListeners.clear();
    mAppFilterListeners.clear();
    mAppLoadListeners.clear();
    mSendRequestListeners.clear();
  }
  
  /**
   * Deletes previous request data stored on the user's device. 
   * This may include zips and files.
   */
  public void deleteRequestData()
  {
    // Create file instances based on save locations
    File saveLocation = new File(mSettings.getSaveLocation());
    File saveLocation2 = new File(mSettings.getSaveLocation2());
    
    // Delete previous zips and files, if any.
    deleteDirectory(saveLocation);
    deleteDirectory(saveLocation2);
  }
  
  /**
   * Attempts to cancel execution of this task. 
   * This attempt will fail if the task has already completed, 
   * already been cancelled, or could not be cancelled for some other reason. 
   * If the task has already started, then the mayInterruptIfRunning parameter 
   * determines whether the thread executing this task should be interrupted 
   * in an attempt to stop the task.
   * <p>
   * This applies only if you called the <b>Async</b> variant.
   * 
   * @param mayInterruptIfRunning
   */
  public void cancelLoadingTask(boolean mayInterruptIfRunning)
  {
    this.loadTask.cancel(mayInterruptIfRunning);
    this.initLoadingTask();
  }

  /**
   * Attempts to cancel execution of this task. 
   * This attempt will fail if the task has already completed, 
   * already been cancelled, or could not be cancelled for some other reason. 
   * If the task has already started, then the mayInterruptIfRunning parameter 
   * determines whether the thread executing this task should be interrupted 
   * in an attempt to stop the task.
   * <p>
   * This applies only if you called the <b>Async</b> variant.
   * 
   * @param mayInterruptIfRunning
   */
  public void cancelSendTask(boolean mayInterruptIfRunning)
  {
    this.sendTask.cancel(mayInterruptIfRunning);
    this.initSendTask();
  }

  /**
   * Attempts to cancel execution of this task. 
   * This attempt will fail if the task has already completed, 
   * already been cancelled, or could not be cancelled for some other reason. 
   * If the task has already started, then the mayInterruptIfRunning parameter 
   * determines whether the thread executing this task should be interrupted 
   * in an attempt to stop the task.
   * <p>
   * This applies only if you called the <b>Async</b> variant.
   * 
   * @param mayInterruptIfRunning
   */
  public void cancelAutomaticTask(boolean mayInterruptIfRunning)
  {
    this.autoTask.cancel(mayInterruptIfRunning);
    this.initAutomaticTask();
  }
  
  /**
   * Returns a boolean letting you know if apps have been loaded. 
   * This keeps in mind the settings you have set.
   * 
   * @return
   */
  public boolean appsLoaded()
  {
    return !(mInstalledApps.size() == 0 || (mSettings.getFilterDefined() && (mApps.size() == 0 || mDefinedApps.size() == 0)));
  }
  
  /**
   * Returns an ArrayList of AppInfo objects. 
   * If apps have no been filtered, it'll return all installed apps. 
   * Otherwise, this will return a list of filtered apps.
   * 
   * @return
   */
  public List<AppInfo> getApps()
  {
    if(mApps.size() == 0 || !mSettings.getFilterDefined())
      return this.mInstalledApps;
    else
      return this.mApps;
  }
  
  /**
   * Returns an ArrayList of AppInfo objects. 
   * If apps have no been filtered, it'll return all installed apps. 
   * Otherwise, this will return a list of filtered apps.
   * <p>
   * Unless you're implementing your own automatic method, 
   * call "getApps()" instead.
   * @return
   */
  public List<AppInfo> getAutomaticApps()
  {
    if(mApps.size() == 0 || !mSettings.getFilterAutomatic())
      return this.mInstalledApps;
    else
      return this.mApps;
  }
  
  /**
   * Returns an ArrayList of AppInfo objects. 
   * All apps in this list are those installed on the device.
   * 
   * @return
   */
  public List<AppInfo> getInstalledApps()
  {
    return this.mInstalledApps;
  }
  
  /**
   * Returns a String ArrayList containing component info 
   * gathered from the appfilter. This is used to filter 
   * out already-supported apps but you might find it useful 
   * for something else.
   * 
   * @return
   */
  public List<String> getDefinedApps()
  {
    return this.mDefinedApps;
  }
  
  /**
   * Returns the number of requests sent during this session. 
   * This counts canceled & failed requests.
   * 
   * @return
   */
  public int getNumRequestSent()
  {
    return this.numRequestsSent;
  }
  
  /**
   * Returns an integer count with the total number of 
   * apps currently selected. This keeps in mind your settings 
   * and will not work if you don't have the proper reference.
   * 
   * @return int
   */
  public int getNumSelected()
  {
    int count = 0;
    List<AppInfo> mAppList = getApps();
    for(AppInfo mApp : mAppList) {
      if(mApp.isSelected())
        count++;
    }
    
    return count;
  }
  
  /**
   * Selects all AppInfo Objects in the list
   */
  public void selectAll()
  {
    List<AppInfo> mAppList = getApps();
    for(AppInfo mApp : mAppList) {
      mApp.setSelected(true);
    }
  }
  
  /**
   * Deselects all AppInfo Objects in the list
   */
  public void deselectAll()
  {
    List<AppInfo> mAppList = getApps();
    for(AppInfo mApp : mAppList) {
      mApp.setSelected(false);
    }
  }
  
  /**
   * Returns a RequestSettings object with all values set.
   * 
   * @return
   */
  public RequestSettings getSettings()
  {
    return this.mSettings;
  }
  
  /**
   * Applies new settings using your own custom RequestSettings object.
   * 
   * @param settings
   */
  public void setSettings(RequestSettings settings)
  {
    this.mSettings = settings;
  }
  
  /**
   * References your current activity. This is only used as a hacky workaround 
   * for when the send intent doesn't automatically open on some devices. 
   * <p>
   * You can also use a OnRequestSendListener to catch the send intent and 
   * start it manually instead of using this workaround.
   * 
   * @param activity
   */
  public void setActivity(Activity activity)
  {
    this.mActivity = activity;
  }
  
  /**
   * Set the debug status for this manager.
   * If true, it will periodically print logs of current 
   * progress so you can see what's going on.
   * <p>
   * I suggest you disable this during production as it
   * will consume unnecessary processing power. Besides,
   * you don't want to spam your users' logs.
   * 
   * @param debug
   */
  public void setDebugging(boolean debug)
  {
    this.debugEnabled = debug;
  }
  
  /**
   * Loads and parses the appfilter.xml for already-
   * defined app info. This helps filter out apps already 
   * supported in your theme.
   */
  private void loadDefinedAppInfo()
  {
    try{
      // Initialize XmlPullParser and set the appfilter.xml (from assets) as the input
      XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance();
      XmlPullParser mParser = xmlFactoryObject.newPullParser();
      AssetManager assManager = mContext.getAssets();
      InputStream inputStream = assManager.open(mSettings.getAppfilterName());
      mParser.setInput(inputStream, null);
      
      // Loop through all listeners notifying them
          for(AppFilterListener mListener : mAppFilterListeners) {
            mListener.onAppPrefilter();
          }
      
      // Read number of elements for progress
      int numElements = 0;
      try {
        InputStream testStream = assManager.open(mSettings.getAppfilterName());
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document doc = docBuilder.parse(testStream);
        NodeList list = doc.getElementsByTagName("item");
        numElements = list.getLength();
      }
      catch (Exception e) {
        if(debugEnabled) {
          Log.d(LOG_TAG, "Error finding the number of appfilter elements!");
          e.printStackTrace();
        }
      }
      
      if(debugEnabled)
        Log.d(LOG_TAG, "" + numElements + " items found in appfilter.");
      
      // Clear defined apps before adding more
      mDefinedApps.clear();
      
      // Keep track of the event type. Also, reuse one string for memory purposes
      String mAppCode = null;
      int eventType = mParser.getEventType();
      
      // Loop through ALL the appfilter
      int count = 0;
      int progress = 0;
      while (eventType != XmlPullParser.END_DOCUMENT) {
        if (eventType == XmlPullParser.START_TAG)
        {
          String elementName= mParser.getName();
          if (elementName.equals("item")) {
            count++;
            progress = (int) (count * MAX_PROGRESS) / numElements;
            // Loop through all listeners notifying them
            for(AppLoadListener mListener : mAppLoadListeners) {
                  mListener.onAppLoading(STATUS_LOADING_APPFILTER, (int) ((MAX_PROGRESS / 3) + (progress / 3)));
                }
                for(AppFilterListener mListener : mAppFilterListeners) {
                  mListener.onAppFiltering(progress, 0);
                }
            try  {
              // Read package and activity name
              mAppCode = mParser.getAttributeValue(null, "component");
              mAppCode = mAppCode.substring(14, mAppCode.length() - 1);
              
              // Add new info to our ArrayList and reset the object. Log commented out to reduce logcat spam.
              mDefinedApps.add(mAppCode);
              //if(debugEnabled)
              //  Log.d(LOG_TAG, "Added appfilter app:\n" + mAppCode);
              mAppCode = null;
              
            }
            catch(Exception e) {
              // Log and ignore whatever individual error was found. 
              if(debugEnabled) {
                Log.e(LOG_TAG, "Error adding parsed appfilter item!");
                e.printStackTrace();
              }
            }
          }
        }
        eventType = mParser.next();
      }
    }
    catch(IOException eIO) {
      if(debugEnabled)
        Log.e(LOG_TAG, "Unable to read appfilter.xml! Are you sure you have in it your assets folder?");
    }
    catch(XmlPullParserException eXPPE) {
      if(debugEnabled) {
        Log.e(LOG_TAG, "Unknown XmlPullParserException! This may be caused by a malformed appfilter.xml. Make sure it's fine.");
        eXPPE.printStackTrace();
      }
    }
  }
  
  /**
   * Loads locally installed apps. These are the ones that will 
   * eventually be requested.
   */
  private void loadInstalledAppInfo()
  {
    // Create package manager and sort it
    PackageManager pm = mContext.getPackageManager();
    List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
    Collections.sort(packages, new ApplicationInfo.DisplayNameComparator(pm));
    
    // Loop through all listeners notifying them
    for(AppLoadListener mListener : mAppLoadListeners) {
          mListener.onAppLoading(STATUS_PRELOAD, 0);
        }
        for(AppLoadListener mListener : mAppLoadListeners) {
          mListener.onAppPreload();
        }
    
    // Loop through all listeners notifying them
        for(InstalledAppLoadListener mListener : mInstalledAppLoadListeners) {
          mListener.onInstalledAppsPreload();
        }
    
    // Clear installed apps before reloading them
    mInstalledApps.clear();
    
    // Reuse app info object for memory purposes. Same goes for other string values.
    AppInfo mAppInfo = null;
    Intent launchIntent = null;
    String launchStr = null;
    String appCode = null;
    String[] splitCode = null;

    // Loops through the package manager to find all installed apps
    final boolean filterDefined = mSettings.getFilterDefined();
    final int numPackages = packages.size();
    int progress = 0;
    for (ApplicationInfo packageInfo : packages) {
      // Examine only valid packages
      launchIntent = pm.getLaunchIntentForPackage(packageInfo.packageName);
      if (launchIntent == null || launchIntent.equals("")) {
        continue;
      }
      
      // Loop through all listeners notifying them
      progress = (int) (packages.indexOf(packageInfo) * MAX_PROGRESS) / numPackages;
          for(AppLoadListener mListener : mAppLoadListeners) {
            mListener.onAppLoading(STATUS_LOADING_INSTALLED, filterDefined ? (int)(progress / 3) : progress);
          }
          for(InstalledAppLoadListener mListener : mInstalledAppLoadListeners) {
            mListener.onInstalledAppsLoading(progress);
          }
      
      // Initialize reusable AppInfo object with default values
      mAppInfo = new AppInfo();
      
      // Set app information from the package manager
      mAppInfo.setImage(packageInfo.loadIcon(pm));
      mAppInfo.setName(packageInfo.loadLabel(pm).toString());
      
      // Get app launch intent and trim it
      launchStr = launchIntent.toString().split("cmp=")[1];
      appCode = launchStr.substring(0, launchStr.length() - 1);
      splitCode = appCode.split("/");
      if (splitCode[1].startsWith("."))
        appCode = splitCode[0] + "/" + splitCode[0] + splitCode[1];
      appCode = appCode.trim();
      
      // Set the trimmed app code
      mAppInfo.setCode(appCode);

      // Add new info to our ArrayList. Log commented out to reduce logcat spam.
      mInstalledApps.add(mAppInfo);
      //if(debugEnabled)
      //  Log.d(LOG_TAG, "Added installed app:\n" + mAppInfo.toString());

      // Nullify objects to make the job easier for the GC
      mAppInfo = null;
      launchStr = null;
      appCode = null;
      splitCode = null;
    }
    
    // Loop through all listeners notifying them
        for(InstalledAppLoadListener mListener : mInstalledAppLoadListeners) {
          mListener.onInstalledAppsLoaded();
        }
        if(!filterDefined) {
          for(AppLoadListener mListener : mAppLoadListeners) {
              mListener.onAppLoading(STATUS_LOADED, MAX_PROGRESS);
            }
        }
  }
  
  /**
   * Filters apps based on your appfilter components. 
   * All filtered apps are copied over to the "mApps" ArrayList.
   */
  private void filterAppInfo()
  {
    // Clear list of filtered apps before adding more
    mApps.clear();
    
    // Loop through all installed apps
    final int numApps = mInstalledApps.size();
    int progress = 0;
    for(AppInfo mAppInfo : mInstalledApps) {
      // Loop through all listeners notifying them
      progress = (int) (mInstalledApps.indexOf(mAppInfo) * MAX_PROGRESS) / numApps;
      for(AppLoadListener mListener : mAppLoadListeners) {
            mListener.onAppLoading(STATUS_FILTERING, (int) ((MAX_PROGRESS / 3 * 2) + (progress / 3)));
          }
      for(AppFilterListener mListener : mAppFilterListeners) {
            mListener.onAppFiltering(MAX_PROGRESS, progress);
          }
      
      // Check if app is already defined and filter it accordingly
      if(!mDefinedApps.contains(mAppInfo.getCode())) {
        mApps.add(mAppInfo);
        //if(debugEnabled)
        //  Log.d(LOG_TAG, "Filtered IN : " + mAppInfo.getName());
      }
      //else if(debugEnabled)
      //  Log.d(LOG_TAG, "Filtered OUT : " + mAppInfo.getName());
    }
    
    if(debugEnabled)
      Log.d(LOG_TAG, "Finished filtering apps!");
    
    // Loop through all listeners notifying them
    for(AppLoadListener mListener : mAppLoadListeners) {
          mListener.onAppLoading(STATUS_LOADED, MAX_PROGRESS);
        }
    for(AppLoadListener mListener : mAppLoadListeners) {
          mListener.onAppLoaded();
        }
    for(AppFilterListener mListener : mAppFilterListeners) {
          mListener.onAppFiltered();
        }
  }
  
  /**
   * Created a .zip file... nuff said.
   * 
   * @param path
   * @param keepDirectoryStructure
   * @param outputFile
   * @return
   */
  private boolean createZipFile(String path, boolean keepDirectoryStructure, String outputFile)
  {
    File f = new File(path);
    if (!f.canRead() || !f.canWrite()) {
      if(debugEnabled)
        Log.d(LOG_TAG, path + " cannot be compressed due to file permissions!");
      return false;
    }
    try {
      ZipOutputStream zip_out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile), mSettings.getByteBuffer()));
      if (keepDirectoryStructure) {
        zipFile(path, zip_out, "");
      }
      else {
        final File files[] = f.listFiles();
        for (final File file : files) {
          zipFolder(file, zip_out);
        }
      }
      zip_out.close();
    } 
    catch (FileNotFoundException e) {
      if(debugEnabled)
        Log.e("File not found: ", e.getMessage());
      
      return false;
    } 
    catch (IOException e) {
      if(debugEnabled)
        Log.e("IOException: ", e.getMessage());
      
      return false;
    }
    return true;
  }
  
  /**
   * Zips a file. Does this really need any explanation?
   * 
   * @param path
   * @param out
   * @param relPath
   * @throws IOException
   */
  private void zipFile(String path, ZipOutputStream out, String relPath) throws IOException 
  {
    File file = new File(path);
    if(!file.exists()) {
      if(debugEnabled)
        Log.d(LOG_TAG, file.getName() + " does NOT exist!");
      return;
    }
    
    final byte[] buffer = new byte[mSettings.getByteBuffer()];
    final String[] files = file.list();
    if(file.isFile()) {   
      FileInputStream in = new FileInputStream(file.getAbsolutePath()); 

      try {
        out.putNextEntry(new ZipEntry(relPath + file.getName()));
        int len; 
        while ((len = in.read(buffer)) > 0)  { 
          out.write(buffer, 0, len); 
        }
        
        out.closeEntry(); 
        in.close();
      }
      catch (ZipException zipE) {
        if(debugEnabled)
          Log.e(LOG_TAG, zipE.getMessage());
      }
      finally {
        if(out != null)
          out.closeEntry(); 
        if(in != null)
          in.close();
      }
    }
    else if (files.length > 0) // non-empty folder
    {
      for (int i = 0, length = files.length; i < length; ++i)
      {
        zipFile(path + "/" + files[i], out, relPath + file.getName() + "/");
      }
    }
  }
  
  /**
   * Zips a folder...
   * 
   * @param file
   * @param zout
   * @throws IOException
   */
  private void zipFolder(File file, ZipOutputStream zout) throws IOException
  {
    byte[] data = new byte[mSettings.getByteBuffer()];
    if(file.isFile()) {
      ZipEntry entry = new ZipEntry(file.getName());
      zout.putNextEntry(entry);
      BufferedInputStream instream = new BufferedInputStream(new FileInputStream(file));
      int read;
      
      while((read = instream.read(data, 0, mSettings.getByteBuffer())) != -1) {
        zout.write(data, 0, read);
      }
      
      zout.closeEntry();
      instream.close();
    }
    else if (file.isDirectory()) {
      String[] list = file.list();
      int len = list.length;
      
      for(int i = 0; i < len; i++)
        zipFolder(new File(file.getPath() +"/"+ list[i]), zout);
    }
  }
  
  /**
   * Deletes files one by one until the entire 
   * directory is deleted.
   * 
   * @param path
   * @return
   */
  private boolean deleteDirectory(File path)
  {
    if(path.exists()) {
      File[] files = path.listFiles();
      for(int i=0; i<files.length; i++) {
        if(files[i].isDirectory())
          deleteDirectory(files[i]);
        else
          files[i].delete();
      }
    }
    
    return(path.delete());
  }
  
  /**
   * Attempts to retrieve the highest resolution icon 
   * available. Returns the high res drawable if successfull; 
   * null if unsuccessful.
   * 
   * @param app
   * @return
   */
  @SuppressLint("InlinedApi")
  private Drawable getHighResDrawable(AppInfo app)
  {
    PackageManager mPackageManager = mContext.getPackageManager();
    ActivityInfo info;
    Resources mRes;
    
    try {
      info = mPackageManager.getActivityInfo(new ComponentName(app.getCode().split("/")[0], app.getCode().split("/")[1]), PackageManager.GET_META_DATA);
      mRes = mPackageManager.getResourcesForApplication(info.applicationInfo);
    }
    catch (Exception e) {
      if(debugEnabled)
        Log.d(LOG_TAG, "Unable to retrieve ActivityInfo for app " + app.getName());
      return null;
    }
    
    if(mRes == null)
      return null;
    
    try {
      int iconId = info.getIconResource();
      if(iconId == 0)
        return null;
      
      return mRes.getDrawableForDensity(iconId, DisplayMetrics.DENSITY_XXXHIGH);
    }
    catch(Exception e) {
      if(debugEnabled)
        Log.d(LOG_TAG, "Unable trying to get density drawable for app" + app.getName());
      return null;
    }
  }
  
  /**
   * Converts a Drawable object into a Bitmap object.
   * 
   * @param drawable
   * @return
   */
  private Bitmap drawableToBitmap(Drawable drawable)
  {
    if (drawable instanceof BitmapDrawable) {
          return ((BitmapDrawable)drawable).getBitmap();
      }

      Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
      Canvas canvas = new Canvas(bitmap); 
      drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
      drawable.draw(canvas);

      return bitmap;
  }
  
  /**
   * Formats drawable names for your generated appfilter.xml
   * 
   * @param appName
   * @return
   */
  private String convertDrawableName(String appName)
  {
    return (appName
        .replaceAll("[^a-zA-Z0-9\\p{Z}]", "")  // Remove all special characters and symbols
        .replaceFirst("^[0-9]+(?!$)", "")    // Remove all leading numbers unless they're all numbers
        .toLowerCase(Locale.US)          // Turn everything into lower case
        .replaceAll("\\p{Z}", "_"));      // Replace all kinds of spaces with underscores
  }
  
  /**
   * Initializes our loading thread.
   */
  private void initLoadingTask()
  {
    this.loadTask = new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(Void... params) {
        loadApps();
        
        return null;
      }
      
      @Override
      protected void onPostExecute(Void p)
      {
        initLoadingTask();
      }
    };
  }
  
  /**
   * Initializes our send request thread.
   */
  private void initSendTask()
  {
    this.sendTask = new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(Void... params) {
        sendRequest();
        
        return null;
      }
      
      @Override
      protected void onPostExecute(Void p)
      {
        initSendTask();
      }
    };
  }
  
  /**
   * Initializes our send request thread.
   */
  private void initAutomaticTask()
  {
    this.autoTask = new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(Void... params) {
        sendAutomaticRequest();
        
        return null;
      }
      
      @Override
      protected void onPostExecute(Void p)
      {
        initAutomaticTask();
      }
    };
  }
}




Java Source Code List

com.pkmmte.requestmanager.AppFilterListener.java
com.pkmmte.requestmanager.AppInfo.java
com.pkmmte.requestmanager.AppLoadListener.java
com.pkmmte.requestmanager.InstalledAppLoadListener.java
com.pkmmte.requestmanager.PkRequestManager.java
com.pkmmte.requestmanager.RequestSettings.java
com.pkmmte.requestmanager.SendRequestListener.java
com.pkmmte.requestmanager.Static.java
com.pkmmte.requestmanager.sample.AdvancedActivity.java
com.pkmmte.requestmanager.sample.AutomaticActivity.java
com.pkmmte.requestmanager.sample.BasicActivity.java
com.pkmmte.requestmanager.sample.IntermediateActivity.java
com.pkmmte.requestmanager.sample.MainActivity.java
com.pkmmte.requestmanager.sample.QuickScroll.java