Skip to content

Instantly share code, notes, and snippets.

@moust
Last active November 12, 2019 04:34
Show Gist options
  • Save moust/7990925 to your computer and use it in GitHub Desktop.
Save moust/7990925 to your computer and use it in GitHub Desktop.
Example of implementation of APK Expansion Files download process with a PhoneGap application
package com.phonegap.Sample;
import java.io.File;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Environment;
import android.os.Messenger;
import android.os.StatFs;
import android.util.Log;
import org.apache.cordova.*;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
public class Sample extends DroidGap implements IDownloaderClient
{
private IStub mDownloaderClientStub;
private IDownloaderService mRemoteService;
private ProgressDialog mProgressDialog;
private static final String LOG_TAG = "Sample";
// The shared path to all app expansion files
private final static String EXP_PATH = "/Android/obb/";
/**
* This is a little helper class that demonstrates simple testing of an
* Expansion APK file delivered by Market. You may not wish to hard-code
* things such as file lengths into your executable... and you may wish to
* turn this code off during application development.
*/
private static class XAPKFile {
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
/**
* Here is where you place the data that the validator will use to determine
* if the file was delivered correctly. This is encoded in the source code
* so the application can easily determine whether the file has been
* properly delivered without having to talk to the server. If the
* application is using LVL for licensing, it may make sense to eliminate
* these checks and to just rely on the server.
*/
private static final XAPKFile[] xAPKS = {
new XAPKFile(
true, // true signifies a main file
1, // the version of the APK that the file was uploaded against
172667765L // the length of the file in bytes
)
};
/**
* Go through each of the APK Expansion files defined in the structure above
* and determine if the files are present and match the required size. Free
* applications should definitely consider doing this, as this allows the
* application to be launched for the first time without having a network
* connection present. Paid applications that use LVL should probably do at
* least one LVL check that requires the network to be present, so this is
* not as necessary.
*
* @return true if they are present.
*/
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
// Log.v(LOG_TAG, "XAPKFile name : " + fileName);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false)) {
Log.e(LOG_TAG, "ExpansionAPKFile doesn't exist or has a wrong size (" + fileName + ").");
return false;
}
}
return true;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Check if expansion files are available before going any further
if (!expansionFilesDelivered()) {
try {
Intent launchIntent = this.getIntent();
// Build an Intent to start this activity from the Notification
Intent notifierIntent = new Intent(Sample.this, Sample.this.getClass());
notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
notifierIntent.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null) {
for (String category : launchIntent.getCategories()) {
notifierIntent.addCategory(category);
}
}
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// Start the download service (if required)
Log.v(LOG_TAG, "Start the download service");
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class);
// If download has started, initialize activity to show progress
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
Log.v(LOG_TAG, "initialize activity to show progress");
// Instantiate a member instance of IStub
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService.class);
// Shows download progress
mProgressDialog = new ProgressDialog(Sample.this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setMessage(getResources().getString(R.string.downloading_assets));
mProgressDialog.setCancelable(false);
mProgressDialog.show();
return;
}
// If the download wasn't necessary, fall through to start the app
else {
Log.v(LOG_TAG, "No download required");
}
}
catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Cannot find own package! MAYDAY!");
e.printStackTrace();
}
catch (Exception e) {
Log.e(LOG_TAG, e.getMessage());
e.printStackTrace();
}
}
// Set by <content src="index.html" /> in config.xml
super.loadUrl(Config.getStartUrl());
//super.loadUrl("file:///android_asset/www/index.html")
}
/**
* Connect the stub to our service on start.
*/
@Override
protected void onStart() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onStart();
}
/**
* Connect the stub from our service on resume
*/
@Override
protected void onResume() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onResume();
}
/**
* Disconnect the stub from our service on stop
*/
@Override
protected void onStop() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}
@Override
public void onServiceConnected(Messenger m) {
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
@Override
public void onDownloadProgress(DownloadProgressInfo progress) {
long percents = progress.mOverallProgress * 100 / progress.mOverallTotal;
Log.v(LOG_TAG, "DownloadProgress:"+Long.toString(percents) + "%");
mProgressDialog.setProgress((int) percents);
}
@Override
public void onDownloadStateChanged(int newState) {
Log.v(LOG_TAG, "DownloadStateChanged : " + getString(Helpers.getDownloaderStringResourceIDFromState(newState)));
switch (newState) {
case STATE_DOWNLOADING:
Log.v(LOG_TAG, "Downloading...");
break;
case STATE_COMPLETED: // The download was finished
// validateXAPKZipFiles();
mProgressDialog.setMessage(getResources().getString(R.string.preparing_assets));
// dismiss progress dialog
mProgressDialog.dismiss();
// Load url
super.loadUrl(Config.getStartUrl());
break;
case STATE_FAILED_UNLICENSED:
case STATE_FAILED_FETCHING_URL:
case STATE_FAILED_SDCARD_FULL:
case STATE_FAILED_CANCELED:
case STATE_FAILED:
Builder alert = new AlertDialog.Builder(this);
alert.setTitle(getResources().getString(R.string.error));
alert.setMessage(getResources().getString(R.string.download_failed));
alert.setNeutralButton(getResources().getString(R.string.close), null);
alert.show();
break;
}
}
}
package com.phonegap.Sample;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
/**
* You should start your derived downloader class when this receiver gets the message
* from the alarm service using the provided service helper function within the
* DownloaderClientMarshaller. This class must be then registered in your AndroidManifest.xml
* file with a section like this:
* <receiver android:name=".XAPKAlarmReceiver"/>
*/
public class XAPKAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, XAPKDownloaderService.class);
} catch (NameNotFoundException e) {S
e.printStackTrace();
}
}
}
package com.phonegap.Sample;
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
/**
* This class demonstrates the minimal client implementation of the
* DownloaderService from the Downloader library.
*/
public class XAPKDownloaderService extends DownloaderService {
// stuff for LVL -- MODIFY FOR YOUR APPLICATION!
private static final String BASE64_PUBLIC_KEY = "YOUR_PUBLIC_KEY";
// used by the preference obfuscater
private static final byte[] SALT = new byte[] {
1, 43, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -108, -33, 45, -1, 84
};
/**
* This public key comes from your Android Market publisher account, and it
* used by the LVL to validate responses from Market on your behalf.
*/
@Override
public String getPublicKey() {
return BASE64_PUBLIC_KEY;
}
/**
* This is used by the preference obfuscater to make sure that your
* obfuscated preferences are different than the ones used by other
* applications.
*/
@Override
public byte[] getSALT() {
return SALT;
}
/**
* Fill this in with the class name for your alarm receiver. We do this
* because receivers must be unique across all of Android (it's a good idea
* to make sure that your receiver is in your unique package)
*/
@Override
public String getAlarmReceiverClassName() {
return XAPKAlarmReceiver.class.getName();
}
}
@dansmart
Copy link

This is great, thanks!

@amh2010
Copy link

amh2010 commented Dec 22, 2014

Good one, thanks

@hojak99
Copy link

hojak99 commented Sep 21, 2016

Thanks!

@tusharmehta02
Copy link

Can you please tell me, how to reduce apk size as it is currently showing apk size+expansion file size on play store which is humongous..Is there any way to do this as in my case it is near about 300 mb??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment