Skip to content

Instantly share code, notes, and snippets.

@engmsaleh
Last active August 29, 2015 14:10
Show Gist options
  • Save engmsaleh/d5fec4389f707c3d1e0c to your computer and use it in GitHub Desktop.
Save engmsaleh/d5fec4389f707c3d1e0c to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="vertical" >
<TextView
android:id="@+id/statusText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/downloaderDashboard"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" >
<TextView
android:id="@+id/progressAsFraction"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="5dp"
android:text="0MB / 0MB" >
</TextView>
<TextView
android:id="@+id/progressAsPercentage"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/progressBar"
android:text="0%" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/progressAsFraction"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp" />
<TextView
android:id="@+id/progressAverageSpeed"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/progressBar"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/progressTimeRemaining"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/progressBar"
android:layout_below="@+id/progressBar" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal" >
<Button
android:id="@+id/pauseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="5dp"
android:layout_marginTop="10dp"
android:layout_weight="0"
android:minHeight="40dp"
android:minWidth="94dp"
android:text="@string/text_button_pause" />
<Button
android:id="@+id/cancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="10dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="10dp"
android:layout_weight="0"
android:minHeight="40dp"
android:minWidth="94dp"
android:text="@string/text_button_cancel"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/approveCellular"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="gone" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:id="@+id/textPausedParagraph1"
android:text="@string/text_paused_cellular" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:id="@+id/textPausedParagraph2"
android:text="@string/text_paused_cellular_2" />
<LinearLayout
android:id="@+id/buttonRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/resumeOverCellular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:text="@string/text_button_resume_cellular" />
<Button
android:id="@+id/wifiSettingsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:text="@string/text_button_wifi_settings" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
package kr.pe.meinside.android.apkx;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.zip.CRC32;
import kr.pe.meinside.android.apkx.sampleapp.R;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Messenger;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.vending.expansion.zipfile.ZipResourceFile;
import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
import com.google.android.vending.expansion.downloader.Constants;
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;
/**
* * referenced: <b>http://developer.android.com/google/play/expansion-files.html</b>
*
* <br><br>
*
* * needs: APK Expansion Download Library, APK Expansion Zip Library, Google Play Service Library, and LVL Library.
*
* <br><br>
*
* 1. Add CheckerActivity.java, DownloadService.java, and DownloadAlarmReceiver.java to the project.
* <br>
* Also add res/values/strings.xml and res/layout/apkx_checker.xml for the convenience.
*
* <br><br>
*
* 2. Extend classes added above.
*
* <br><br>
*
* 3. Register extended activity, service, and receiver in the manifest file:
*
* <pre>
* &lt;activity android:name="YOUR_CheckerActivity" /&gt;
* &lt;service android:name="YOUR_DownloadService" /&gt;
* &lt;receiver android:name="YOUR_DownloadAlarmReceiver" /&gt;
* </pre>
*
* <br>
*
* 4. and add following permissions:
* <pre>
* &lt;!-- Required to access Android Market Licensing --&gt;
* &lt;uses-permission android:name="com.android.vending.CHECK_LICENSE" /&gt;
* &lt;!-- Required to download files from Android Market --&gt;
* &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
* &lt;!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --&gt;
* &lt;uses-permission android:name="android.permission.WAKE_LOCK" /&gt;
* &lt;!-- Required to poll the state of the network connection and respond to changes --&gt;
* &lt;uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /&gt;
* &lt;!-- Required to check whether Wi-Fi is enabled --&gt;
* &lt;uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/&gt;
* &lt;!-- Required to read and write the expansion files on shared storage --&gt;
* &lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
* </pre>
*
* <br>
*
* 5. Add following element inside <application> element of Manifest.xml file
*
* <br><br>
*
* <pre>
* &lt;meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /&gt;
* </pre>
*
* <br>
*
* 6. Build .obo files and upload them with the application .apk file.
*
* <pre>
* # (example) zip files without compression
* $ zip -0 main.1.com.sample.app.obo ./*.mp3
* </pre>
*
* @author meinside@gmail.com
*
*/
public abstract class CheckerActivity extends Activity implements IDownloaderClient
{
private static final String LOG_TAG = "APKXChecker";
private ProgressBar mPB;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private View mDashboard;
private View mCellMessage;
private Button mPauseButton;
private Button mWiFiSettingsButton;
private boolean mStatePaused;
private int mState;
private IStub mDownloaderClientStub;
private IDownloaderService mRemoteService;
/**
* Calculating a moving average for the validation speed so we don't get
* jumpy calculations for time etc.
*/
static private final float SMOOTHING_FACTOR = 0.005f;
/**
* Used by the async task
*/
private boolean mCancelValidation;
private void setState(int newState)
{
if (mState != newState)
{
mState = newState;
mStatusText.setText(getStateString(newState));
}
}
private int getStateString(int state)
{
int stateRes;
switch (state)
{
case IDownloaderClient.STATE_IDLE:
stateRes = R.string.state_idle;
break;
case IDownloaderClient.STATE_FETCHING_URL:
stateRes = R.string.state_fetching_url;
break;
case IDownloaderClient.STATE_CONNECTING:
stateRes = R.string.state_connecting;
break;
case IDownloaderClient.STATE_DOWNLOADING:
stateRes = R.string.state_downloading;
break;
case IDownloaderClient.STATE_COMPLETED:
stateRes = R.string.state_completed;
break;
case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
stateRes = R.string.state_paused_network_unavailable;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
stateRes = R.string.state_paused_by_request;
break;
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
stateRes = R.string.state_paused_wifi_disabled;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
stateRes = R.string.state_paused_wifi_unavailable;
break;
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
stateRes = R.string.state_paused_wifi_disabled;
break;
case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
stateRes = R.string.state_paused_wifi_unavailable;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
stateRes = R.string.state_paused_roaming;
break;
case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
stateRes = R.string.state_paused_network_setup_failure;
break;
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
stateRes = R.string.state_paused_sdcard_unavailable;
break;
case IDownloaderClient.STATE_FAILED_UNLICENSED:
stateRes = R.string.state_failed_unlicensed;
break;
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
stateRes = R.string.state_failed_fetching_url;
break;
case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
stateRes = R.string.state_failed_sdcard_full;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
stateRes = R.string.state_failed_cancelled;
break;
default:
stateRes = R.string.state_unknown;
break;
}
return stateRes;
}
private void setButtonPausedState(boolean paused)
{
mStatePaused = paused;
int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
/**
* Both downloading and validation make use of the "download" UI
*/
initializeDownloadUI();
/**
* Before we do anything, are the files we expect already here and
* delivered (presumably by Market) For free titles, this is probably
* worth doing. (so no Market request is necessary)
*/
if (!expansionFilesDelivered())
{
try
{
Intent launchIntent = CheckerActivity.this.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(CheckerActivity.this, getActivityClass());
intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null)
{
for (String category : launchIntent.getCategories())
{
intentToLaunchThisActivityFromNotification.addCategory(category);
}
}
// Build PendingIntent used to open this activity from Notification
PendingIntent pendingIntent = PendingIntent.getActivity(CheckerActivity.this, 0, intentToLaunchThisActivityFromNotification, PendingIntent.FLAG_UPDATE_CURRENT);
// Request to start the download
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, getDownloadServiceClass());
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED)
{
// The DownloaderService has started downloading the files, show progress
initializeDownloadUI();
return;
} // otherwise, download not needed so we fall through to starting the movie
}
catch (NameNotFoundException e)
{
Log.e(LOG_TAG, "Cannot find own package! MAYDAY!");
e.printStackTrace();
}
}
else
{
validateXAPKZipFiles();
}
}
@Override
protected void onStart()
{
if (null != mDownloaderClientStub)
{
mDownloaderClientStub.connect(this);
}
super.onStart();
}
@Override
protected void onStop()
{
if (null != mDownloaderClientStub)
{
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}
@Override
protected void onDestroy()
{
this.mCancelValidation = true;
super.onDestroy();
}
/**
* 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.
*/
public static class XAPKFile
{
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
public XAPKFile(boolean isMain, int fileVersion, long fileSize)
{
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
/**
* 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 : getXAPKs())
{
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true;
}
@Override
public void onServiceConnected(Messenger m)
{
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
@Override
public void onDownloadStateChanged(int newState)
{
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState) {
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's safe to start making calls via mRemoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
validateXAPKZipFiles();
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard.getVisibility() != newDashboardVisibility) {
mDashboard.setVisibility(newDashboardVisibility);
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility) {
mCellMessage.setVisibility(cellMessageVisibility);
}
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
@Override
public void onDownloadProgress(DownloadProgressInfo progress)
{
mAverageSpeed.setText(getString(R.string.kilobytes_per_second, Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining, Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
mPB.setMax((int) (progress.mOverallTotal >> 8));
mPB.setProgress((int) (progress.mOverallProgress >> 8));
mProgressPercent.setText(Long.toString(progress.mOverallProgress * 100 / progress.mOverallTotal) + "%");
mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
}
/**
* Go through each of the Expansion APK files and open each as a zip file.
* Calculate the CRC for each file and return false if any fail to match.
*
* @return true if XAPKZipFile is successful
*/
void validateXAPKZipFiles()
{
AsyncTask<Object, DownloadProgressInfo, Boolean> validationTask = new AsyncTask<Object, DownloadProgressInfo, Boolean>(){
@Override
protected void onPreExecute()
{
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_verifying_download);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
mCancelValidation = true;
}
});
mPauseButton.setText(R.string.text_button_cancel_verify);
super.onPreExecute();
}
@Override
protected Boolean doInBackground(Object... params)
{
for (XAPKFile xf : getXAPKs())
{
String fileName = Helpers.getExpansionAPKFileName(CheckerActivity.this, xf.mIsMain, xf.mFileVersion);
Log.v(LOG_TAG, "checking for filename: " + fileName);
if (!Helpers.doesFileExist(CheckerActivity.this, fileName, xf.mFileSize, false))
return false;
fileName = Helpers.generateSaveFileName(CheckerActivity.this, fileName);
ZipResourceFile zrf;
byte[] buf = new byte[1024 * 256];
try
{
zrf = new ZipResourceFile(fileName);
ZipEntryRO[] entries = zrf.getAllEntries();
/**
* First calculate the total compressed length
*/
long totalCompressedLength = 0;
for (ZipEntryRO entry : entries)
{
totalCompressedLength += entry.mCompressedLength;
}
float averageVerifySpeed = 0;
long totalBytesRemaining = totalCompressedLength;
long timeRemaining;
/**
* Then calculate a CRC for every file in the Zip file,
* comparing it to what is stored in the Zip directory.
* Note that for compressed Zip files we must extract
* the contents to do this comparison.
*/
for (ZipEntryRO entry : entries)
{
if (-1 != entry.mCRC32)
{
long length = entry.mUncompressedLength;
CRC32 crc = new CRC32();
DataInputStream dis = null;
try
{
dis = new DataInputStream(zrf.getInputStream(entry.mFileName));
long startTime = SystemClock.uptimeMillis();
while (length > 0)
{
int seek = (int) (length > buf.length ? buf.length : length);
dis.readFully(buf, 0, seek);
crc.update(buf, 0, seek);
length -= seek;
long currentTime = SystemClock.uptimeMillis();
long timePassed = currentTime - startTime;
if (timePassed > 0)
{
float currentSpeedSample = (float) seek / (float) timePassed;
if (0 != averageVerifySpeed)
{
averageVerifySpeed = SMOOTHING_FACTOR * currentSpeedSample + (1 - SMOOTHING_FACTOR) * averageVerifySpeed;
}
else
{
averageVerifySpeed = currentSpeedSample;
}
totalBytesRemaining -= seek;
timeRemaining = (long) (totalBytesRemaining / averageVerifySpeed);
this.publishProgress(
new DownloadProgressInfo(
totalCompressedLength,
totalCompressedLength - totalBytesRemaining,
timeRemaining,
averageVerifySpeed)
);
}
startTime = currentTime;
if (mCancelValidation)
return true;
}
if (crc.getValue() != entry.mCRC32)
{
Log.e(Constants.TAG, "CRC does not match for entry: " + entry.mFileName);
Log.e(Constants.TAG, "In file: " + entry.getZipFileName());
return false;
}
}
finally
{
if (null != dis)
{
dis.close();
}
}
}
}
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
}
return true;
}
@Override
protected void onProgressUpdate(DownloadProgressInfo... values)
{
onDownloadProgress(values[0]);
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean result)
{
if (result)
{
mDashboard.setVisibility(View.INVISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_complete);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
finish();
}
});
mPauseButton.setText(android.R.string.ok);
onCheckSuccess(true);
}
else
{
mDashboard.setVisibility(View.INVISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_failed);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
finish();
}
});
mPauseButton.setText(android.R.string.cancel);
onCheckSuccess(false);
}
super.onPostExecute(result);
}
};
validationTask.execute(new Object());
}
/**
* If the download isn't present, we initialize the download UI. This ties
* all of the controls into the remote service calls.
*/
private void initializeDownloadUI()
{
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, getDownloadServiceClass());
setContentView(R.layout.apkx_checker);
mPB = (ProgressBar) findViewById(R.id.progressBar);
mStatusText = (TextView) findViewById(R.id.statusText);
mProgressFraction = (TextView) findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView) findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView) findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView) findViewById(R.id.progressTimeRemaining);
mDashboard = findViewById(R.id.downloaderDashboard);
mCellMessage = findViewById(R.id.approveCellular);
mPauseButton = (Button) findViewById(R.id.pauseButton);
mWiFiSettingsButton = (Button) findViewById(R.id.wifiSettingsButton);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
if (mStatePaused)
{
mRemoteService.requestContinueDownload();
}
else
{
mRemoteService.requestPauseDownload();
}
setButtonPausedState(!mStatePaused);
}
});
mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular);
resumeOnCell.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
mRemoteService.requestContinueDownload();
mCellMessage.setVisibility(View.GONE);
}
});
}
/**
* return information of expansion APKs
* <br>
* <br>
* * example:
*
* <pre>
* return new XAPKFile[]{
* new XAPKFile(
* true, // true signifies a main file
* 10101001, // the version of the APK that the file was uploaded against
* 80611837L // the length of the file in bytes
* ),
* new XAPKFile(
* false, // false signifies a patch file
* 10101001, // the version of the APK that the patch file was uploaded against
* 512860L // the length of the patch file in bytes
* )
* };
* </pre>
*
* @return an array of Expansion APK information
*/
protected abstract XAPKFile[] getXAPKs();
/**
*
* @return
*/
@SuppressWarnings("rawtypes")
protected abstract Class getActivityClass();
/**
*
* @return
*/
@SuppressWarnings("rawtypes")
protected abstract Class getDownloadServiceClass();
/**
*
* @param success
*/
protected abstract void onCheckSuccess(boolean success);
}
package kr.pe.meinside.android.apkx;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
public abstract class DownloadAlarmReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
try
{
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, getDownloadServiceClass());
}
catch (NameNotFoundException e)
{
e.printStackTrace();
}
}
@SuppressWarnings("rawtypes")
abstract public Class getDownloadServiceClass();
}
package kr.pe.meinside.android.apkx;
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
public abstract class DownloadService extends DownloaderService
{
@Override
abstract public String getPublicKey();
@Override
abstract public byte[] getSALT();
@Override
abstract public String getAlarmReceiverClassName();
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">APK Expansion Download Sample</string>
<string name="text_paused_cellular">Would you like to enable downloading over cellular connections? Depending on your data plan, this may cost you money.</string>
<string name="text_paused_cellular_2">If you choose not to enable downloading over cellular connections, the download will automatically resume when Wi-Fi is available.</string>
<string name="text_button_resume_cellular">Resume download</string>
<string name="text_button_wifi_settings">Wi-Fi settings</string>
<string name="text_verifying_download">Verifying Download</string>
<string name="text_validation_complete">Resource File Validation Complete.</string>
<string name="text_validation_failed">Resource File Validation Failed.</string>
<string name="text_button_pause">Pause Download</string>
<string name="text_button_resume">Resume Download</string>
<string name="text_button_cancel">Cancel</string>
<string name="text_button_cancel_verify">Cancel Verification</string>
<string name="notification_download_complete">Download complete</string>
<string name="notification_download_failed">Download unsuccessful</string>
<string name="state_unknown"></string>
<string name="state_idle">Waiting for download to start</string>
<string name="state_fetching_url">Looking for resources to download</string>
<string name="state_connecting">Connecting to the download server</string>
<string name="state_downloading">Downloading resources</string>
<string name="state_completed">Download finished</string>
<string name="state_paused_network_unavailable">Download paused because no network is available</string>
<string name="state_paused_network_setup_failure">Download paused. Test a website in browser</string>
<string name="state_paused_by_request">Download paused</string>
<string name="state_paused_wifi_unavailable">Download paused because wifi is unavailable</string>
<string name="state_paused_wifi_disabled">Download paused because wifi is disabled</string>
<string name="state_paused_roaming">Download paused because you are roaming</string>
<string name="state_paused_sdcard_unavailable">Download paused because the external storage is unavailable</string>
<string name="state_failed_unlicensed">Download failed because you may not have purchased this app</string>
<string name="state_failed_fetching_url">Download failed because the resources could not be found</string>
<string name="state_failed_sdcard_full">Download failed because the external storage is full</string>
<string name="state_failed_cancelled">Download cancelled</string>
<string name="state_failed">Download failed</string>
<string name="kilobytes_per_second">%1$s KB/s</string>
<string name="time_remaining">Time remaining: %1$s</string>
<string name="time_remaining_notification">%1$s left</string>
</resources>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment