Last active
August 29, 2015 14:10
-
-
Save engmsaleh/d5fec4389f707c3d1e0c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
* <activity android:name="YOUR_CheckerActivity" /> | |
* <service android:name="YOUR_DownloadService" /> | |
* <receiver android:name="YOUR_DownloadAlarmReceiver" /> | |
* </pre> | |
* | |
* <br> | |
* | |
* 4. and add following permissions: | |
* <pre> | |
* <!-- Required to access Android Market Licensing --> | |
* <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> | |
* <!-- Required to download files from Android Market --> | |
* <uses-permission android:name="android.permission.INTERNET" /> | |
* <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> | |
* <uses-permission android:name="android.permission.WAKE_LOCK" /> | |
* <!-- Required to poll the state of the network connection and respond to changes --> | |
* <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |
* <!-- Required to check whether Wi-Fi is enabled --> | |
* <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> | |
* <!-- Required to read and write the expansion files on shared storage --> | |
* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |
* </pre> | |
* | |
* <br> | |
* | |
* 5. Add following element inside <application> element of Manifest.xml file | |
* | |
* <br><br> | |
* | |
* <pre> | |
* <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> | |
* </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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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