2012/04/03

APK Expansion Files

Development environment.
  • Ubuntu 11.10 (Oneiric)
  • JDK 1.6.0_24-b07
  • Eclipse 3.7.3 (Indigo)
    • Android Development Toolkit 17.0.0

Preparing.
  1. Get a publisher account here for Google Play, if you don't have yet.
  2. Download two packages from the SDK Manager.('Google Play APK Expansion Library' and 'Google Play Licensing Library')
    Android SDK Manager
  3. Create two library projects from existing source.
    • <sdk>/extras/google/play_apk_expansion/downloader_library
    • <sdk>/extras/google/play_licensing/library
  4. Create a new project which you want to use APK Expansion Files and add above two library project to your new project's build path.
    Add a reference to a library project

Implementing.
  1. Downloader service.
    // Extends com.google.android.vending.expansion.downloader.impl.DownloaderService class and override three methods.
    public class ExpansionFileDownloaderService extends DownloaderService {
     // the Base64-encoded RSA public key for your publisher account
     private static final String PUBLIC_KEY = "{YOU_PUBLIC_KEY}";
     // Generate 20 random bytes, and put them here.
     private static final byte[] SALT = new byte[] {};
     @Override public String getPublicKey() {
      return PUBLIC_KEY;
     }
     @Override public byte[] getSALT() {
      return SALT;
     }
     @Override public String getAlarmReceiverClassName() {
      return ExpansionFileAlarmReceiver.class.getName();
     }
    }
    
  2. Alarm receiver.
    public class ExpansionFileAlarmReceiver extends BroadcastReceiver {
     @Override public void onReceive(Context context, Intent intent) {
      try {
       DownloaderClientMarshaller.startDownloadServiceIfRequired(
         context, intent, ExpansionFileDownloaderService.class);
      } catch (NameNotFoundException e) {
       Log.e("apk-expansion-files", "NameNotFoundException occurred. " + e.getMessage(), e);
      }
     }
    }
    
  3. Main activity.
    @Override
     public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      
      // Check whether the file have been downloaded.
      String mainFileName = Helpers.getExpansionAPKFileName(this, true, 1);
      boolean fileExists = Helpers.doesFileExist(this, mainFileName, 32921796L, false);
      if (!fileExists) {
       Intent launcher = getIntent();
       Intent fromNotification = new Intent(this, getClass());
       fromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
       fromNotification.setAction(launcher.getAction());
       if (launcher.getCategories() != null) {
        for (String cat : launcher.getCategories()) {
         fromNotification.addCategory(cat);
        }
       }
       PendingIntent pendingIntent = PendingIntent.getActivity(
         this, 0, fromNotification, PendingIntent.FLAG_UPDATE_CURRENT);
       try {
        // Start the download
        int result = DownloaderClientMarshaller.startDownloadServiceIfRequired(
          this, pendingIntent, ExpansionFileDownloaderService.class);
        if (DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED != result) {
         // implement Downloading UI.
         return;
        }
       } catch (NameNotFoundException e) {
        Log.e("apk-expansion-files", "NameNotFoundException occurred. " + e.getMessage(), e);
       }
      }
      // expansion file is available. start your application.
     }
    

Manifest declarations.
  • uses-permission
    <!-- 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" />
    
  • application
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".ExpansionFileDownloaderService" android:label="@string/app_name" />
        <receiver android:name=".ExpansionFileAlarmReceiver" android:label="@string/app_name" />
    </application>
    

Testing APK Expansion Files
  1. Upload the application APK file and Expansion Files using the Google Play Developer Console.
    Uploading APK and Expansion file.
  2. Fill in the necessary application details. Click the Save button. Do not click Publish.
  3. Install the application on your test device and launch the app. Download will start soon.

11 件のコメント :

  1. /extras/google/play_apk_expansion/downloader_library

    i tried to import downloder library but I am getting red line on import statement below;
    import com.android.vending.expansion.downloader.R;

    also "R cannot be resolved to a variable" problem on some of the classes like Helper.java, DownloaderService.java, DownloaderNotification, V11CustomNotification & V3CustomNotification.

    it would be really kind of you if can show steps to import those projects.

    返信削除
    返信
    1. Please make sure that the 'downloader_library' project is set up as a 'Android library project'. If not, R.class is never created in Gen directory. See also: http://developer.android.com/guide/developing/projects/index.html#LibraryProjects

      削除
  2. Thanks a lot for your reply!

    I checked, the ‘downloader_library' project is set up as 'Android library project'.

    Before I setting the target of the as 2.2 therefore I was getting an errors on several classes then when I tried to import with default target, an errors disappear.

    But when I actually linked the ‘downloader_library’ with my project I getting following errors;
    /android-sdk-macosx/extras/google/play_apk_expansion/downloader_library/res/values-v9/styles.xml:3: error: Error retrieving parent for item: No resource found that matches the given name 'android:TextAppearance.StatusBar.EventContent'.

    [2012-04-26 10:04:41 - abc] /Users/mq/Documents/android-sdk-macosx/extras/google/play_apk_expansion/downloader_library/res/values-v9/styles.xml:4: error: Error retrieving parent for item: No resource found that matches the given name 'android:TextAppearance.StatusBar.EventContent.Title'.


    I noticed that I when remove ‘values-v9’ folder from downloader library project all the errors seems to disappear from my actual app.

    I guessing its definitely something to do with values-v9’ folder. Currently my application target set to Android 2.2, API LEVEL 8 and ‘downloader_library' API Android 4.0.3, API LEVEL 15.


    If you don’t mind could you please upload your above sample projects as zip?

    返信削除
    返信
    1. Okey, i uploaded my sample project below.
      http://maiware.net/samples/

      削除
  3. I have a question. How can I handle the moment when download is completed?

    返信削除
    返信
    1. When download is completed, "DownloaderService" class will send a "PendingIntent". This "Intent" class is "DownloaderClientMarshaller#startDownloadServiceIfRequired" method's second parameter. (see also: http://developer.android.com/guide/google/play/expansion-files.html "2.Start the download by calling the static method"

      削除
  4. Hi Thanks for the tutorial when am trying to run your project by using my publisher account key , i am getting exception like following
    can you please help me to resolve this and i have uploaded build to my account as draft and uploaded main expansion file.

    11-23 13:14:57.030: E/AndroidRuntime(3271): java.lang.IllegalArgumentException: salt.length == 0
    11-23 13:14:57.030: E/AndroidRuntime(3271): at javax.crypto.spec.PBEKeySpec.(PBEKeySpec.java:79)

    返信削除
    返信
    1. Ah... see "ExpansionFileDownloaderService.java" (extends DownloaderService). And implement "SALT" variable like this,
      "public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84};".

      削除
    2. Hi thanks working now and after downloaded succesfully the expansion files how can i read them ,my expansion files contains images.could you please suggest me.

      Thanks in Advance...

      削除
  5. Hi ,

    I have added apk expansion files to my app ,and am downloading apk expansion files sucessfully, i have only main expansion files ,and when am trying to read apk expansion files am getting stream as null.

    Note : My apk expansion files having images.While uoploading i have zipped the images and uploaded.

    Can anyone please help me out.

    ZipResourceFile expansionFile = APKExpansionSupport
    .getAPKExpansionZipFile(context, 1, 0);

    // Get an input stream for a known file inside the expansion file
    // ZIPs

    InputStream is = expansionFile.getInputStream("95065668.jpg");
    Log.d(TAG,
    "Assigning obb image to preview image::::::::::::"
    + is.available());
    BitmapFactory.Options bfo = new BitmapFactory.Options();
    bfo.inPreferredConfig = Bitmap.Config.ARGB_8888;
    Bitmap b = BitmapFactory.decodeStream(is, null, bfo);

    myimage.setBitmap(b);

    返信削除
  6. Hello! I have a question, how do I get the public key from the keystore?

    返信削除