├── zip_file
├── res
│ └── .nores
├── AndroidManifest.xml
├── project.properties
└── src
│ └── com
│ └── google
│ └── android
│ └── vending
│ └── expansion
│ └── zipfile
│ ├── APKExpansionSupport.java
│ ├── APEZProvider.java
│ └── ZipResourceFile.java
├── apkx_library
├── libs
│ └── android-support-v4.jar
├── project.properties
├── AndroidManifest.xml
├── src
│ └── com
│ │ └── google
│ │ └── android
│ │ └── vending
│ │ └── expansion
│ │ └── downloader
│ │ ├── IStub.java
│ │ ├── DownloadProgressInfo.java
│ │ ├── IDownloaderService.java
│ │ ├── impl
│ │ ├── DownloadInfo.java
│ │ ├── CustomIntentService.java
│ │ ├── HttpDateTime.java
│ │ ├── DownloadNotification.java
│ │ └── DownloadsDB.java
│ │ ├── SystemFacade.java
│ │ ├── IDownloaderClient.java
│ │ ├── DownloaderServiceMarshaller.java
│ │ ├── Constants.java
│ │ ├── DownloaderClientMarshaller.java
│ │ └── Helpers.java
└── res
│ └── values
│ └── strings.xml
├── apkx_sample
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── values
│ │ ├── styles.xml
│ │ └── strings.xml
│ ├── values-v11
│ │ └── styles.xml
│ └── layout
│ │ ├── videoplayer.xml
│ │ └── main.xml
├── README.txt
├── project.properties
├── src
│ └── com
│ │ └── example
│ │ └── google
│ │ └── play
│ │ └── apkx
│ │ ├── SampleZipFileProvider.java
│ │ ├── SampleAlarmReceiver.java
│ │ ├── SampleDownloaderService.java
│ │ └── SampleVideoPlayerActivity.java
└── AndroidManifest.xml
├── .gitignore
├── CONTRIBUTING.md
├── README.txt
├── LICENSE
└── package.xml
/zip_file/res/.nores:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apkx_library/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/play-apk-expansion/HEAD/apkx_library/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/apkx_sample/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/play-apk-expansion/HEAD/apkx_sample/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apkx_sample/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/play-apk-expansion/HEAD/apkx_sample/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/apkx_sample/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/play-apk-expansion/HEAD/apkx_sample/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/apkx_sample/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apkx_sample/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/apkx_sample/README.txt:
--------------------------------------------------------------------------------
1 | Sample for the APK Expansion File downloader, featuring use of the ZipFile content provider.
2 |
3 | This sample has four library dependencies:
4 |
5 | 1) The APK Expansion File Downloader (which is, in itself, dependent on the License Validation Library)
6 | 2) The ZipFile Content Provider
7 | 3) The AppCompat-v7 Library
8 | 4) The Design Library
9 |
--------------------------------------------------------------------------------
/zip_file/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/zip_file/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-23
12 | android.library=true
13 |
--------------------------------------------------------------------------------
/apkx_library/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | android.library=true
11 | # Project target.
12 | target=android-23
13 | android.library.reference.1=../../play-licensing/lvl_library
14 |
15 |
--------------------------------------------------------------------------------
/apkx_library/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apkx_sample/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Note. This application requires both the support (v7) library and
11 | # the design library as references. (not in this file)
12 |
13 | # Project target.
14 | target=android-23
15 | android.library.reference.1=../apkx_library
16 | android.library.reference.2=../zip_file
17 | manifestmerger.enabled=true
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .DS_Store
3 |
4 | # built application files
5 | *.apk
6 | *.ap_
7 |
8 | # files for the dex VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # generated files
15 | bin/
16 | out/
17 | gen/
18 |
19 | # Libraries used by the app
20 | # Can explicitly add if we want, but shouldn't do so blindly. Licenses, bloat, etc.
21 | /libs
22 |
23 |
24 | # Build stuff (auto-generated by android update project ...)
25 | build.xml
26 | ant.properties
27 | local.properties
28 |
29 | # Eclipse project files
30 | .classpath
31 | .project
32 |
33 | # idea project files
34 | .idea/
35 | .idea/.name
36 | *.iml
37 | *.ipr
38 | *.iws
39 |
40 | ##Gradle-based build
41 | .gradle
42 | build/
43 |
--------------------------------------------------------------------------------
/apkx_sample/res/layout/videoplayer.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
15 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/apkx_sample/src/com/example/google/play/apkx/SampleZipFileProvider.java:
--------------------------------------------------------------------------------
1 |
2 | package com.example.google.play.apkx;
3 |
4 | /*
5 | * Copyright (C) 2012 The Android Open Source Project
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 | import android.net.Uri;
20 |
21 | public class SampleZipFileProvider extends com.google.android.vending.expansion.zipfile.APEZProvider {
22 | // main content provider URI
23 | private static final String CONTENT_PREFIX = "content://";
24 |
25 | // must match what is declared in the Zip content provider in
26 | // the AndroidManifest.xml file
27 | private static final String AUTHORITY = "com.example.google.play.apkx";
28 |
29 | public static final Uri ASSET_URI = Uri.parse(CONTENT_PREFIX + AUTHORITY);
30 |
31 | public String getAuthority() {
32 | return AUTHORITY;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Want to contribute? Great! First, read this page (including the small print at the end).
2 |
3 | ### Before you contribute
4 | Before we can use your code, you must sign the
5 | [Google Individual Contributor License Agreement]
6 | (https://cla.developers.google.com/about/google-individual)
7 | (CLA), which you can do online. The CLA is necessary mainly because you own the
8 | copyright to your changes, even after your contribution becomes part of our
9 | codebase, so we need your permission to use and distribute your code. We also
10 | need to be sure of various other things—for instance that you'll tell us if you
11 | know that your code infringes on other people's patents. You don't have to sign
12 | the CLA until after you've submitted your code for review and a member has
13 | approved it, but you must do it before we can put your code into our codebase.
14 | Before you start working on a larger contribution, you should get in touch with
15 | us first through the issue tracker with your idea so that we can help out and
16 | possibly guide you. Coordinating up front makes it much easier to avoid
17 | frustration later on.
18 |
19 | ### Code reviews
20 | All submissions, including submissions by project members, require review. We
21 | use Github pull requests for this purpose.
22 |
23 | ### The small print
24 | Contributions made by corporations are covered by a different agreement than
25 | the one above, the
26 | [Software Grant and Corporate Contributor License Agreement]
27 | (https://cla.developers.google.com/about/google-corporate).
28 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/IStub.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import android.content.Context;
20 | import android.os.Messenger;
21 |
22 | /**
23 | * This is the interface that is used to connect/disconnect from the downloader
24 | * service.
25 | *
26 | * You should get a proxy object that implements this interface by calling
27 | * {@link DownloaderClientMarshaller#CreateStub} in your activity when the
28 | * downloader service starts. Then, call {@link #connect} during your activity's
29 | * onResume() and call {@link #disconnect} during onStop().
30 | *
31 | * Then during the {@link IDownloaderClient#onServiceConnected} callback, you
32 | * should call {@link #getMessenger} to pass the stub's Messenger object to
33 | * {@link IDownloaderService#onClientUpdated}.
34 | */
35 | public interface IStub {
36 | Messenger getMessenger();
37 |
38 | void connect(Context c);
39 |
40 | void disconnect(Context c);
41 | }
42 |
--------------------------------------------------------------------------------
/apkx_sample/src/com/example/google/play/apkx/SampleAlarmReceiver.java:
--------------------------------------------------------------------------------
1 | package com.example.google.play.apkx;
2 | /*
3 | * Copyright (C) 2012 The Android Open Source Project
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
18 |
19 | import android.content.BroadcastReceiver;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.content.pm.PackageManager.NameNotFoundException;
23 |
24 | /**
25 | * You should start your derived downloader class when this receiver gets the message
26 | * from the alarm service using the provided service helper function within the
27 | * DownloaderClientMarshaller. This class must be then registered in your AndroidManifest.xml
28 | * file with a section like this:
29 | *
30 | */
31 | public class SampleAlarmReceiver extends BroadcastReceiver {
32 |
33 | @Override
34 | public void onReceive(Context context, Intent intent) {
35 | try {
36 | DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, SampleDownloaderService.class);
37 | } catch (NameNotFoundException e) {
38 | e.printStackTrace();
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/apkx_sample/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | APKX Downloader
4 | Would you like to enable downloading over cellular connections? Depending on your data plan, this may cost you money.
5 | If you choose not to enable downloading over cellular connections, the download will automatically resume when wi-fi is available.
6 | Resume download
7 | Wi-Fi settings
8 | Verifying Download
9 | XAPK File Validation Complete. Select OK to start the movie.
10 | XAPK File Validation Failed.
11 | Pause Download
12 | Resume Download
13 | Cancel
14 | Cancel Verification
15 |
16 | READ_EXTERNAL_STORAGE permission request was denied.
17 | READ_EXTERNAL_STORAGE permission is required to read expansion files.
18 | "Permission is not available. Requesting READ_EXTERNAL_STORAGE permission."
19 |
20 | WRITE_EXTERNAL_STORAGE permission request was denied.
21 | WRITE_EXTERNAL_STORAGE permission is required to download expansion files.
22 | "Permission is not available. Requesting WRITE_EXTERNAL_STORAGE permission."
23 |
24 |
--------------------------------------------------------------------------------
/apkx_sample/src/com/example/google/play/apkx/SampleDownloaderService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.google.play.apkx;
18 |
19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService;
20 |
21 | /**
22 | * This class demonstrates the minimal client implementation of the
23 | * DownloaderService from the Downloader library.
24 | */
25 | public class SampleDownloaderService extends DownloaderService {
26 | // stuff for LVL -- MODIFY FOR YOUR APPLICATION!
27 | private static final String BASE64_PUBLIC_KEY = "REPLACE THIS WITH YOUR PUBLIC KEY";
28 | // used by the preference obfuscater
29 | private static final byte[] SALT = new byte[] {
30 | 1, 43, -12, -1, 54, 98,
31 | -100, -12, 43, 2, -8, -4, 9, 5, -106, -108, -33, 45, -1, 84
32 | };
33 |
34 | /**
35 | * This public key comes from your Android Market publisher account, and it
36 | * used by the LVL to validate responses from Market on your behalf.
37 | */
38 | @Override
39 | public String getPublicKey() {
40 | return BASE64_PUBLIC_KEY;
41 | }
42 |
43 | /**
44 | * This is used by the preference obfuscater to make sure that your
45 | * obfuscated preferences are different than the ones used by other
46 | * applications.
47 | */
48 | @Override
49 | public byte[] getSALT() {
50 | return SALT;
51 | }
52 |
53 | /**
54 | * Fill this in with the class name for your alarm receiver. We do this
55 | * because receivers must be unique across all of Android (it's a good idea
56 | * to make sure that your receiver is in your unique package)
57 | */
58 | @Override
59 | public String getAlarmReceiverClassName() {
60 | return SampleAlarmReceiver.class.getName();
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/apkx_library/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 | Download complete
10 |
11 |
16 | Download unsuccessful
17 |
18 |
19 | Starting..."
20 | Waiting for download to start
21 | Looking for resources to download
22 | Connecting to the download server
23 | Downloading resources
24 | Download finished
25 | Download paused because no network is available
26 | Download paused. Test a website in browser
27 | Download paused
28 | Download paused because wifi is unavailable
29 | Download paused because wifi is disabled
30 | Download paused because you are roaming
31 | Download paused because the external storage is unavailable
32 | Download failed because you may not have purchased this app
33 | Download failed because the resources could not be found
34 | Download failed because the external storage is full
35 | Download cancelled
36 | Download failed
37 |
38 | %1$s KB/s
39 | Time remaining: %1$s
40 | %1$s left
41 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import android.os.Parcel;
20 | import android.os.Parcelable;
21 |
22 |
23 | /**
24 | * This class contains progress information about the active download(s).
25 | *
26 | * When you build the Activity that initiates a download and tracks the
27 | * progress by implementing the {@link IDownloaderClient} interface, you'll
28 | * receive a DownloadProgressInfo object in each call to the {@link
29 | * IDownloaderClient#onDownloadProgress} method. This allows you to update
30 | * your activity's UI with information about the download progress, such
31 | * as the progress so far, time remaining and current speed.
32 | */
33 | public class DownloadProgressInfo implements Parcelable {
34 | public long mOverallTotal;
35 | public long mOverallProgress;
36 | public long mTimeRemaining; // time remaining
37 | public float mCurrentSpeed; // speed in KB/S
38 |
39 | @Override
40 | public int describeContents() {
41 | return 0;
42 | }
43 |
44 | @Override
45 | public void writeToParcel(Parcel p, int i) {
46 | p.writeLong(mOverallTotal);
47 | p.writeLong(mOverallProgress);
48 | p.writeLong(mTimeRemaining);
49 | p.writeFloat(mCurrentSpeed);
50 | }
51 |
52 | public DownloadProgressInfo(Parcel p) {
53 | mOverallTotal = p.readLong();
54 | mOverallProgress = p.readLong();
55 | mTimeRemaining = p.readLong();
56 | mCurrentSpeed = p.readFloat();
57 | }
58 |
59 | public DownloadProgressInfo(long overallTotal, long overallProgress,
60 | long timeRemaining,
61 | float currentSpeed) {
62 | this.mOverallTotal = overallTotal;
63 | this.mOverallProgress = overallProgress;
64 | this.mTimeRemaining = timeRemaining;
65 | this.mCurrentSpeed = currentSpeed;
66 | }
67 |
68 | public static final Creator CREATOR = new Creator() {
69 | @Override
70 | public DownloadProgressInfo createFromParcel(Parcel parcel) {
71 | return new DownloadProgressInfo(parcel);
72 | }
73 |
74 | @Override
75 | public DownloadProgressInfo[] newArray(int i) {
76 | return new DownloadProgressInfo[i];
77 | }
78 | };
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/zip_file/src/com/google/android/vending/expansion/zipfile/APKExpansionSupport.java:
--------------------------------------------------------------------------------
1 | package com.google.android.vending.expansion.zipfile;
2 | /*
3 | * Copyright (C) 2012 The Android Open Source Project
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | import java.io.File;
18 | import java.io.FileFilter;
19 | import java.io.IOException;
20 | import java.util.Vector;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | import android.content.Context;
25 | import android.os.Environment;
26 |
27 | public class APKExpansionSupport {
28 | // The shared path to all app expansion files
29 | private final static String EXP_PATH = "/Android/obb/";
30 |
31 | static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
32 | String packageName = ctx.getPackageName();
33 | Vector ret = new Vector();
34 | if (Environment.getExternalStorageState().equals(
35 | Environment.MEDIA_MOUNTED)) {
36 | // Build the full path to the app's expansion files
37 | File root = Environment.getExternalStorageDirectory();
38 | File expPath = new File(root.toString() + EXP_PATH + packageName);
39 |
40 | // Check that expansion file path exists
41 | if (expPath.exists()) {
42 | if ( mainVersion > 0 ) {
43 | String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb";
44 | File main = new File(strMainPath);
45 | if ( main.isFile() ) {
46 | ret.add(strMainPath);
47 | }
48 | }
49 | if ( patchVersion > 0 ) {
50 | String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb";
51 | File main = new File(strPatchPath);
52 | if ( main.isFile() ) {
53 | ret.add(strPatchPath);
54 | }
55 | }
56 | }
57 | }
58 | String[] retArray = new String[ret.size()];
59 | ret.toArray(retArray);
60 | return retArray;
61 | }
62 |
63 | static public ZipResourceFile getResourceZipFile(String[] expansionFiles) throws IOException {
64 | ZipResourceFile apkExpansionFile = null;
65 | for (String expansionFilePath : expansionFiles) {
66 | if ( null == apkExpansionFile ) {
67 | apkExpansionFile = new ZipResourceFile(expansionFilePath);
68 | } else {
69 | apkExpansionFile.addPatchFile(expansionFilePath);
70 | }
71 | }
72 | return apkExpansionFile;
73 | }
74 |
75 | static public ZipResourceFile getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) throws IOException{
76 | String[] expansionFiles = getAPKExpansionFiles(ctx, mainVersion, patchVersion);
77 | return getResourceZipFile(expansionFiles);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/IDownloaderService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService;
20 | import android.os.Messenger;
21 |
22 | /**
23 | * This interface is implemented by the DownloaderService and by the
24 | * DownloaderServiceMarshaller. It contains functions to control the service.
25 | * When a client binds to the service, it must call the onClientUpdated
26 | * function.
27 | *
28 | * You can acquire a proxy that implements this interface for your service by
29 | * calling {@link DownloaderServiceMarshaller#CreateProxy} during the
30 | * {@link IDownloaderClient#onServiceConnected} callback. At which point, you
31 | * should immediately call {@link #onClientUpdated}.
32 | */
33 | public interface IDownloaderService {
34 | /**
35 | * Set this flag in response to the
36 | * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then
37 | * call RequestContinueDownload to resume a download
38 | */
39 | public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1;
40 |
41 | /**
42 | * Request that the service abort the current download. The service should
43 | * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}.
44 | */
45 | void requestAbortDownload();
46 |
47 | /**
48 | * Request that the service pause the current download. The service should
49 | * respond by changing the state to
50 | * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
51 | */
52 | void requestPauseDownload();
53 |
54 | /**
55 | * Request that the service continue a paused download, when in any paused
56 | * or failed state, including
57 | * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
58 | */
59 | void requestContinueDownload();
60 |
61 | /**
62 | * Set the flags for this download (e.g.
63 | * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}).
64 | *
65 | * @param flags
66 | */
67 | void setDownloadFlags(int flags);
68 |
69 | /**
70 | * Requests that the download status be sent to the client.
71 | */
72 | void requestDownloadStatus();
73 |
74 | /**
75 | * Call this when you get {@link
76 | * IDownloaderClient.onServiceConnected(Messenger m)} from the
77 | * DownloaderClient to register the client with the service. It will
78 | * automatically send the current status to the client.
79 | *
80 | * @param clientMessenger
81 | */
82 | void onClientUpdated(Messenger clientMessenger);
83 | }
84 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader.impl;
18 |
19 | import com.google.android.vending.expansion.downloader.Constants;
20 | import com.google.android.vending.expansion.downloader.Helpers;
21 |
22 | import android.util.Log;
23 |
24 | /**
25 | * Representation of information about an individual download from the database.
26 | */
27 | public class DownloadInfo {
28 | public String mUri;
29 | public final int mIndex;
30 | public final String mFileName;
31 | public String mETag;
32 | public long mTotalBytes;
33 | public long mCurrentBytes;
34 | public long mLastMod;
35 | public int mStatus;
36 | public int mControl;
37 | public int mNumFailed;
38 | public int mRetryAfter;
39 | public int mRedirectCount;
40 |
41 | boolean mInitialized;
42 |
43 | public int mFuzz;
44 |
45 | public DownloadInfo(int index, String fileName, String pkg) {
46 | mFuzz = Helpers.sRandom.nextInt(1001);
47 | mFileName = fileName;
48 | mIndex = index;
49 | }
50 |
51 | public void resetDownload() {
52 | mCurrentBytes = 0;
53 | mETag = "";
54 | mLastMod = 0;
55 | mStatus = 0;
56 | mControl = 0;
57 | mNumFailed = 0;
58 | mRetryAfter = 0;
59 | mRedirectCount = 0;
60 | }
61 |
62 | /**
63 | * Returns the time when a download should be restarted.
64 | */
65 | public long restartTime(long now) {
66 | if (mNumFailed == 0) {
67 | return now;
68 | }
69 | if (mRetryAfter > 0) {
70 | return mLastMod + mRetryAfter;
71 | }
72 | return mLastMod +
73 | Constants.RETRY_FIRST_DELAY *
74 | (1000 + mFuzz) * (1 << (mNumFailed - 1));
75 | }
76 |
77 | public void logVerboseInfo() {
78 | Log.v(Constants.TAG, "Service adding new entry");
79 | Log.v(Constants.TAG, "FILENAME: " + mFileName);
80 | Log.v(Constants.TAG, "URI : " + mUri);
81 | Log.v(Constants.TAG, "FILENAME: " + mFileName);
82 | Log.v(Constants.TAG, "CONTROL : " + mControl);
83 | Log.v(Constants.TAG, "STATUS : " + mStatus);
84 | Log.v(Constants.TAG, "FAILED_C: " + mNumFailed);
85 | Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter);
86 | Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount);
87 | Log.v(Constants.TAG, "LAST_MOD: " + mLastMod);
88 | Log.v(Constants.TAG, "TOTAL : " + mTotalBytes);
89 | Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes);
90 | Log.v(Constants.TAG, "ETAG : " + mETag);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/apkx_sample/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
56 |
57 |
58 |
62 |
63 |
64 |
72 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | NOTE
2 | ====
3 |
4 | Support for this library as been discontinued after the launch of Play Asset Delivery (https://developer.android.com/guide/app-bundle/asset-delivery).
5 | Play Asset Delivery is a better solution that requires much less developer effort and is compliant with all SDK versions.
6 |
7 | As announced in [August 2020](https://android-developers.googleblog.com/2020/08/recent-android-app-bundle-improvements.html), new apps and games will be required to be published on Google Play using the Android App Bundle format in [August 2021] (https://android-developers.googleblog.com/2020/11/new-android-app-bundle-and-target-api.html).
8 |
9 | Play Asset Delivery is an integral part in this roadmap and it should be preferred over APK Expansion files for new projects.
10 |
11 | ====
12 |
13 | Client library for the Google Market APK Expansion files.
14 |
15 | Changelog
16 | ---------
17 | Version 5
18 | * Removed pre-V14 compatibility (preparing for more changes)
19 |
20 | Version 4
21 | * Updated for Marshmallow
22 | - No longer uses Apache HTTP
23 | - No longer relies on removed Notification methods
24 | * Changed to refer to Google Play
25 | * Notifications now rely on support-v7 22.+ and support-v4 22.0.+.
26 |
27 | Version 3
28 | * Directory structure corrected in distribution. No code changes.
29 |
30 | Version 2
31 | * Patch file now gets downloaded.
32 | * Honeycomb devices now supported with ICS-like notifications
33 | * CRC check (from sample) now supports compressed Zip files
34 | * Use of reflection removed to allow easy obfuscation
35 | * Service leak fixed
36 | * Unprintable character removed from ZipResourceFile
37 | * Minor formatting changes
38 | * Additional comments and edits to this file
39 |
40 | Packages
41 | --------
42 |
43 | downloader_library
44 | A library that works with the google_market_licensing library to download APK Expansion files in a background service. This library depends on the licensing library, and must be built as a library project.
45 | zip_file
46 | A library that uses multiple zip files as a virtual read-only filesystem with hooks to support APK Expansion files. This also must be built as an Android library project.
47 | downloader_sample
48 | A sample application that assumes that zip format files have been uploaded as the main/patch file(s) on Android Market. It downloads these files and then validates that the CRC's for every entry in the zip match. This application depends on the downloader_library and the zip_file library. Because of dependency issues involving multiple libraries, you may have to do a clean build after creating each project.
49 |
50 | IMPORTANT THINGS TO KNOW
51 | ------------------------
52 |
53 | 1) Do not plan to extract the contents of an APK Expansion file. They are intended to be used in-place. By not compressing audio and video files and storing them in a Zip file they can be played from within an expansion file.
54 | 2) See com.google.android.vending.expansion.downloader/Constants.java to turn on verbose logging in the library. Please turn it off when you publish
55 | 3) You must add your SALT and Public key to the SampleDownloaderService and update the xAPKS structure in SampleDownloaderActivity in order to test with the sample code
56 | 4) There is no strong need to include the validator with your Android applications. It is included as a demonstration, and can be used if you wish.
57 | 5) When using the APEZProvider for retrieving Zip files via "content://" URIs, you may wish to leave them uncompressed. Otherwise the content provider may return null due to failing to grab ahold of a file descriptor.
58 |
59 | For more information, see the documentation at http://developer.android.com/guide/market/expansion-files.html
60 |
61 | This library requires the Android License Verification Library.
62 |
63 | See the licensing documentation at http://developer.android.com/guide/publishing/licensing.html
64 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader.impl;
18 |
19 | import android.app.Service;
20 | import android.content.Intent;
21 | import android.os.Handler;
22 | import android.os.HandlerThread;
23 | import android.os.IBinder;
24 | import android.os.Looper;
25 | import android.os.Message;
26 | import android.util.Log;
27 |
28 | /**
29 | * This service differs from IntentService in a few minor ways/ It will not
30 | * auto-stop itself after the intent is handled unless the target returns "true"
31 | * in should stop. Since the goal of this service is to handle a single kind of
32 | * intent, it does not queue up batches of intents of the same type.
33 | */
34 | public abstract class CustomIntentService extends Service {
35 | private String mName;
36 | private boolean mRedelivery;
37 | private volatile ServiceHandler mServiceHandler;
38 | private volatile Looper mServiceLooper;
39 | private static final String LOG_TAG = "CustomIntentService";
40 | private static final int WHAT_MESSAGE = -10;
41 |
42 | public CustomIntentService(String paramString) {
43 | this.mName = paramString;
44 | }
45 |
46 | @Override
47 | public IBinder onBind(Intent paramIntent) {
48 | return null;
49 | }
50 |
51 | @Override
52 | public void onCreate() {
53 | super.onCreate();
54 | HandlerThread localHandlerThread = new HandlerThread("IntentService["
55 | + this.mName + "]");
56 | localHandlerThread.start();
57 | this.mServiceLooper = localHandlerThread.getLooper();
58 | this.mServiceHandler = new ServiceHandler(this.mServiceLooper);
59 | }
60 |
61 | @Override
62 | public void onDestroy() {
63 | Thread localThread = this.mServiceLooper.getThread();
64 | if ((localThread != null) && (localThread.isAlive())) {
65 | localThread.interrupt();
66 | }
67 | this.mServiceLooper.quit();
68 | Log.d(LOG_TAG, "onDestroy");
69 | }
70 |
71 | protected abstract void onHandleIntent(Intent paramIntent);
72 |
73 | protected abstract boolean shouldStop();
74 |
75 | @Override
76 | public void onStart(Intent paramIntent, int startId) {
77 | if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) {
78 | Message localMessage = this.mServiceHandler.obtainMessage();
79 | localMessage.arg1 = startId;
80 | localMessage.obj = paramIntent;
81 | localMessage.what = WHAT_MESSAGE;
82 | this.mServiceHandler.sendMessage(localMessage);
83 | }
84 | }
85 |
86 | @Override
87 | public int onStartCommand(Intent paramIntent, int flags, int startId) {
88 | onStart(paramIntent, startId);
89 | return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
90 | }
91 |
92 | public void setIntentRedelivery(boolean enabled) {
93 | this.mRedelivery = enabled;
94 | }
95 |
96 | private final class ServiceHandler extends Handler {
97 | public ServiceHandler(Looper looper) {
98 | super(looper);
99 | }
100 |
101 | @Override
102 | public void handleMessage(Message paramMessage) {
103 | CustomIntentService.this
104 | .onHandleIntent((Intent) paramMessage.obj);
105 | if (shouldStop()) {
106 | Log.d(LOG_TAG, "stopSelf");
107 | CustomIntentService.this.stopSelf(paramMessage.arg1);
108 | Log.d(LOG_TAG, "afterStopSelf");
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/SystemFacade.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import android.app.Notification;
20 | import android.app.NotificationManager;
21 | import android.content.Context;
22 | import android.content.Intent;
23 | import android.content.pm.PackageManager.NameNotFoundException;
24 | import android.net.ConnectivityManager;
25 | import android.net.NetworkInfo;
26 | import android.telephony.TelephonyManager;
27 | import android.util.Log;
28 |
29 | /**
30 | * Contains useful helper functions, typically tied to the application context.
31 | */
32 | class SystemFacade {
33 | private Context mContext;
34 | private NotificationManager mNotificationManager;
35 |
36 | public SystemFacade(Context context) {
37 | mContext = context;
38 | mNotificationManager = (NotificationManager)
39 | mContext.getSystemService(Context.NOTIFICATION_SERVICE);
40 | }
41 |
42 | public long currentTimeMillis() {
43 | return System.currentTimeMillis();
44 | }
45 |
46 | public Integer getActiveNetworkType() {
47 | ConnectivityManager connectivity =
48 | (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
49 | if (connectivity == null) {
50 | Log.w(Constants.TAG, "couldn't get connectivity manager");
51 | return null;
52 | }
53 |
54 | NetworkInfo activeInfo = connectivity.getActiveNetworkInfo();
55 | if (activeInfo == null) {
56 | if (Constants.LOGVV) {
57 | Log.v(Constants.TAG, "network is not available");
58 | }
59 | return null;
60 | }
61 | return activeInfo.getType();
62 | }
63 |
64 | public boolean isNetworkRoaming() {
65 | ConnectivityManager connectivity =
66 | (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
67 | if (connectivity == null) {
68 | Log.w(Constants.TAG, "couldn't get connectivity manager");
69 | return false;
70 | }
71 |
72 | NetworkInfo info = connectivity.getActiveNetworkInfo();
73 | boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE);
74 | TelephonyManager tm = (TelephonyManager) mContext
75 | .getSystemService(Context.TELEPHONY_SERVICE);
76 | if (null == tm) {
77 | Log.w(Constants.TAG, "couldn't get telephony manager");
78 | return false;
79 | }
80 | boolean isRoaming = isMobile && tm.isNetworkRoaming();
81 | if (Constants.LOGVV && isRoaming) {
82 | Log.v(Constants.TAG, "network is roaming");
83 | }
84 | return isRoaming;
85 | }
86 |
87 | public Long getMaxBytesOverMobile() {
88 | return (long) Integer.MAX_VALUE;
89 | }
90 |
91 | public Long getRecommendedMaxBytesOverMobile() {
92 | return 2097152L;
93 | }
94 |
95 | public void sendBroadcast(Intent intent) {
96 | mContext.sendBroadcast(intent);
97 | }
98 |
99 | public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException {
100 | return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid;
101 | }
102 |
103 | public void postNotification(long id, Notification notification) {
104 | /**
105 | * TODO: The system notification manager takes ints, not longs, as IDs,
106 | * but the download manager uses IDs take straight from the database,
107 | * which are longs. This will have to be dealt with at some point.
108 | */
109 | mNotificationManager.notify((int) id, notification);
110 | }
111 |
112 | public void cancelNotification(long id) {
113 | mNotificationManager.cancel((int) id);
114 | }
115 |
116 | public void cancelAllNotifications() {
117 | mNotificationManager.cancelAll();
118 | }
119 |
120 | public void startThread(Thread thread) {
121 | thread.start();
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import android.os.Messenger;
20 |
21 | /**
22 | * This interface should be implemented by the client activity for the
23 | * downloader. It is used to pass status from the service to the client.
24 | */
25 | public interface IDownloaderClient {
26 | static final int STATE_IDLE = 1;
27 | static final int STATE_FETCHING_URL = 2;
28 | static final int STATE_CONNECTING = 3;
29 | static final int STATE_DOWNLOADING = 4;
30 | static final int STATE_COMPLETED = 5;
31 |
32 | static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6;
33 | static final int STATE_PAUSED_BY_REQUEST = 7;
34 |
35 | /**
36 | * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and
37 | * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and
38 | * cellular permission will restart the service. Wi-Fi disabled means that
39 | * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the
40 | * other case Wi-Fi is enabled but not available.
41 | */
42 | static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8;
43 | static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9;
44 |
45 | /**
46 | * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that
47 | * Wi-Fi is unavailable and cellular permission will NOT restart the
48 | * service. Wi-Fi disabled means that the Wi-Fi manager is returning that
49 | * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not
50 | * available.
51 | *
52 | * The service does not return these values. We recommend that app
53 | * developers with very large payloads do not allow these payloads to be
54 | * downloaded over cellular connections.
55 | */
56 | static final int STATE_PAUSED_WIFI_DISABLED = 10;
57 | static final int STATE_PAUSED_NEED_WIFI = 11;
58 |
59 | static final int STATE_PAUSED_ROAMING = 12;
60 |
61 | /**
62 | * Scary case. We were on a network that redirected us to another website
63 | * that delivered us the wrong file.
64 | */
65 | static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13;
66 |
67 | static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14;
68 |
69 | static final int STATE_FAILED_UNLICENSED = 15;
70 | static final int STATE_FAILED_FETCHING_URL = 16;
71 | static final int STATE_FAILED_SDCARD_FULL = 17;
72 | static final int STATE_FAILED_CANCELED = 18;
73 |
74 | static final int STATE_FAILED = 19;
75 |
76 | /**
77 | * Called internally by the stub when the service is bound to the client.
78 | *
79 | * Critical implementation detail. In onServiceConnected we create the
80 | * remote service and marshaler. This is how we pass the client information
81 | * back to the service so the client can be properly notified of changes. We
82 | * must do this every time we reconnect to the service.
83 | *
84 | * That is, when you receive this callback, you should call
85 | * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member
86 | * instance of {@link IDownloaderService}, then call
87 | * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved
88 | * from your {@link IStub} proxy object.
89 | *
90 | * @param m the service Messenger. This Messenger is used to call the
91 | * service API from the client.
92 | */
93 | void onServiceConnected(Messenger m);
94 |
95 | /**
96 | * Called when the download state changes. Depending on the state, there may
97 | * be user requests. The service is free to change the download state in the
98 | * middle of a user request, so the client should be able to handle this.
99 | *
100 | * The Downloader Library includes a collection of string resources that
101 | * correspond to each of the states, which you can use to provide users a
102 | * useful message based on the state provided in this callback. To fetch the
103 | * appropriate string for a state, call
104 | * {@link Helpers#getDownloaderStringResourceIDFromState}.
105 | *
106 | * What this means to the developer: The application has gotten a message
107 | * that the download has paused due to lack of WiFi. The developer should
108 | * then show UI asking the user if they want to enable downloading over
109 | * cellular connections with appropriate warnings. If the application
110 | * suddenly starts downloading, the application should revert to showing the
111 | * progress again, rather than leaving up the download over cellular UI up.
112 | *
113 | * @param newState one of the STATE_* values defined in IDownloaderClient
114 | */
115 | void onDownloadStateChanged(int newState);
116 |
117 | /**
118 | * Shows the download progress. This is intended to be used to fill out a
119 | * client UI. This progress should only be shown in a few states such as
120 | * STATE_DOWNLOADING.
121 | *
122 | * @param progress the DownloadProgressInfo object containing the current
123 | * progress of all downloads.
124 | */
125 | void onDownloadProgress(DownloadProgressInfo progress);
126 | }
127 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService;
20 |
21 | import android.content.Context;
22 | import android.os.Bundle;
23 | import android.os.Handler;
24 | import android.os.Message;
25 | import android.os.Messenger;
26 | import android.os.RemoteException;
27 |
28 |
29 |
30 | /**
31 | * This class is used by the client activity to proxy requests to the Downloader
32 | * Service.
33 | *
34 | * Most importantly, you must call {@link #CreateProxy} during the {@link
35 | * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate
36 | * an {@link IDownloaderService} object that you can then use to issue commands to the {@link
37 | * DownloaderService} (such as to pause and resume downloads).
38 | */
39 | public class DownloaderServiceMarshaller {
40 |
41 | public static final int MSG_REQUEST_ABORT_DOWNLOAD =
42 | 1;
43 | public static final int MSG_REQUEST_PAUSE_DOWNLOAD =
44 | 2;
45 | public static final int MSG_SET_DOWNLOAD_FLAGS =
46 | 3;
47 | public static final int MSG_REQUEST_CONTINUE_DOWNLOAD =
48 | 4;
49 | public static final int MSG_REQUEST_DOWNLOAD_STATE =
50 | 5;
51 | public static final int MSG_REQUEST_CLIENT_UPDATE =
52 | 6;
53 |
54 | public static final String PARAMS_FLAGS = "flags";
55 | public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;
56 |
57 | private static class Proxy implements IDownloaderService {
58 | private Messenger mMsg;
59 |
60 | private void send(int method, Bundle params) {
61 | Message m = Message.obtain(null, method);
62 | m.setData(params);
63 | try {
64 | mMsg.send(m);
65 | } catch (RemoteException e) {
66 | e.printStackTrace();
67 | }
68 | }
69 |
70 | public Proxy(Messenger msg) {
71 | mMsg = msg;
72 | }
73 |
74 | @Override
75 | public void requestAbortDownload() {
76 | send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
77 | }
78 |
79 | @Override
80 | public void requestPauseDownload() {
81 | send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
82 | }
83 |
84 | @Override
85 | public void setDownloadFlags(int flags) {
86 | Bundle params = new Bundle();
87 | params.putInt(PARAMS_FLAGS, flags);
88 | send(MSG_SET_DOWNLOAD_FLAGS, params);
89 | }
90 |
91 | @Override
92 | public void requestContinueDownload() {
93 | send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
94 | }
95 |
96 | @Override
97 | public void requestDownloadStatus() {
98 | send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
99 | }
100 |
101 | @Override
102 | public void onClientUpdated(Messenger clientMessenger) {
103 | Bundle bundle = new Bundle(1);
104 | bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
105 | send(MSG_REQUEST_CLIENT_UPDATE, bundle);
106 | }
107 | }
108 |
109 | private static class Stub implements IStub {
110 | private IDownloaderService mItf = null;
111 | final Messenger mMessenger = new Messenger(new Handler() {
112 | @Override
113 | public void handleMessage(Message msg) {
114 | switch (msg.what) {
115 | case MSG_REQUEST_ABORT_DOWNLOAD:
116 | mItf.requestAbortDownload();
117 | break;
118 | case MSG_REQUEST_CONTINUE_DOWNLOAD:
119 | mItf.requestContinueDownload();
120 | break;
121 | case MSG_REQUEST_PAUSE_DOWNLOAD:
122 | mItf.requestPauseDownload();
123 | break;
124 | case MSG_SET_DOWNLOAD_FLAGS:
125 | mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
126 | break;
127 | case MSG_REQUEST_DOWNLOAD_STATE:
128 | mItf.requestDownloadStatus();
129 | break;
130 | case MSG_REQUEST_CLIENT_UPDATE:
131 | mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
132 | PARAM_MESSENGER));
133 | break;
134 | }
135 | }
136 | });
137 |
138 | public Stub(IDownloaderService itf) {
139 | mItf = itf;
140 | }
141 |
142 | @Override
143 | public Messenger getMessenger() {
144 | return mMessenger;
145 | }
146 |
147 | @Override
148 | public void connect(Context c) {
149 |
150 | }
151 |
152 | @Override
153 | public void disconnect(Context c) {
154 |
155 | }
156 | }
157 |
158 | /**
159 | * Returns a proxy that will marshall calls to IDownloaderService methods
160 | *
161 | * @param ctx
162 | * @return
163 | */
164 | public static IDownloaderService CreateProxy(Messenger msg) {
165 | return new Proxy(msg);
166 | }
167 |
168 | /**
169 | * Returns a stub object that, when connected, will listen for marshalled
170 | * IDownloaderService methods and translate them into calls to the supplied
171 | * interface.
172 | *
173 | * @param itf An implementation of IDownloaderService that will be called
174 | * when remote method calls are unmarshalled.
175 | * @return
176 | */
177 | public static IStub CreateStub(IDownloaderService itf) {
178 | return new Stub(itf);
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/apkx_sample/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
22 |
23 |
29 |
30 |
34 |
35 |
43 |
44 |
45 |
52 |
53 |
64 |
65 |
73 |
74 |
81 |
82 |
83 |
88 |
89 |
102 |
103 |
117 |
118 |
119 |
120 |
121 |
128 |
129 |
135 |
136 |
142 |
143 |
148 |
149 |
156 |
157 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader.impl;
18 |
19 | import android.text.format.Time;
20 |
21 | import java.util.Calendar;
22 | import java.util.regex.Matcher;
23 | import java.util.regex.Pattern;
24 |
25 | /**
26 | * Helper for parsing an HTTP date.
27 | */
28 | public final class HttpDateTime {
29 |
30 | /*
31 | * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT
32 | * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850,
33 | * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format
34 | * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon
35 | * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS
36 | * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon
37 | * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first
38 | * digit is zero. Mon can be the full name of the month.
39 | */
40 | private static final String HTTP_DATE_RFC_REGEXP =
41 | "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
42 | + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
43 |
44 | private static final String HTTP_DATE_ANSIC_REGEXP =
45 | "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
46 | + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
47 |
48 | /**
49 | * The compiled version of the HTTP-date regular expressions.
50 | */
51 | private static final Pattern HTTP_DATE_RFC_PATTERN =
52 | Pattern.compile(HTTP_DATE_RFC_REGEXP);
53 | private static final Pattern HTTP_DATE_ANSIC_PATTERN =
54 | Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
55 |
56 | private static class TimeOfDay {
57 | TimeOfDay(int h, int m, int s) {
58 | this.hour = h;
59 | this.minute = m;
60 | this.second = s;
61 | }
62 |
63 | int hour;
64 | int minute;
65 | int second;
66 | }
67 |
68 | public static long parse(String timeString)
69 | throws IllegalArgumentException {
70 |
71 | int date = 1;
72 | int month = Calendar.JANUARY;
73 | int year = 1970;
74 | TimeOfDay timeOfDay;
75 |
76 | Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
77 | if (rfcMatcher.find()) {
78 | date = getDate(rfcMatcher.group(1));
79 | month = getMonth(rfcMatcher.group(2));
80 | year = getYear(rfcMatcher.group(3));
81 | timeOfDay = getTime(rfcMatcher.group(4));
82 | } else {
83 | Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
84 | if (ansicMatcher.find()) {
85 | month = getMonth(ansicMatcher.group(1));
86 | date = getDate(ansicMatcher.group(2));
87 | timeOfDay = getTime(ansicMatcher.group(3));
88 | year = getYear(ansicMatcher.group(4));
89 | } else {
90 | throw new IllegalArgumentException();
91 | }
92 | }
93 |
94 | // FIXME: Y2038 BUG!
95 | if (year >= 2038) {
96 | year = 2038;
97 | month = Calendar.JANUARY;
98 | date = 1;
99 | }
100 |
101 | Time time = new Time(Time.TIMEZONE_UTC);
102 | time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
103 | month, year);
104 | return time.toMillis(false /* use isDst */);
105 | }
106 |
107 | private static int getDate(String dateString) {
108 | if (dateString.length() == 2) {
109 | return (dateString.charAt(0) - '0') * 10
110 | + (dateString.charAt(1) - '0');
111 | } else {
112 | return (dateString.charAt(0) - '0');
113 | }
114 | }
115 |
116 | /*
117 | * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0
118 | * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20
119 | * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19
120 | * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9
121 | */
122 | private static int getMonth(String monthString) {
123 | int hash = Character.toLowerCase(monthString.charAt(0)) +
124 | Character.toLowerCase(monthString.charAt(1)) +
125 | Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
126 | switch (hash) {
127 | case 22:
128 | return Calendar.JANUARY;
129 | case 10:
130 | return Calendar.FEBRUARY;
131 | case 29:
132 | return Calendar.MARCH;
133 | case 32:
134 | return Calendar.APRIL;
135 | case 36:
136 | return Calendar.MAY;
137 | case 42:
138 | return Calendar.JUNE;
139 | case 40:
140 | return Calendar.JULY;
141 | case 26:
142 | return Calendar.AUGUST;
143 | case 37:
144 | return Calendar.SEPTEMBER;
145 | case 35:
146 | return Calendar.OCTOBER;
147 | case 48:
148 | return Calendar.NOVEMBER;
149 | case 9:
150 | return Calendar.DECEMBER;
151 | default:
152 | throw new IllegalArgumentException();
153 | }
154 | }
155 |
156 | private static int getYear(String yearString) {
157 | if (yearString.length() == 2) {
158 | int year = (yearString.charAt(0) - '0') * 10
159 | + (yearString.charAt(1) - '0');
160 | if (year >= 70) {
161 | return year + 1900;
162 | } else {
163 | return year + 2000;
164 | }
165 | } else if (yearString.length() == 3) {
166 | // According to RFC 2822, three digit years should be added to 1900.
167 | int year = (yearString.charAt(0) - '0') * 100
168 | + (yearString.charAt(1) - '0') * 10
169 | + (yearString.charAt(2) - '0');
170 | return year + 1900;
171 | } else if (yearString.length() == 4) {
172 | return (yearString.charAt(0) - '0') * 1000
173 | + (yearString.charAt(1) - '0') * 100
174 | + (yearString.charAt(2) - '0') * 10
175 | + (yearString.charAt(3) - '0');
176 | } else {
177 | return 1970;
178 | }
179 | }
180 |
181 | private static TimeOfDay getTime(String timeString) {
182 | // HH might be H
183 | int i = 0;
184 | int hour = timeString.charAt(i++) - '0';
185 | if (timeString.charAt(i) != ':')
186 | hour = hour * 10 + (timeString.charAt(i++) - '0');
187 | // Skip ':'
188 | i++;
189 |
190 | int minute = (timeString.charAt(i++) - '0') * 10
191 | + (timeString.charAt(i++) - '0');
192 | // Skip ':'
193 | i++;
194 |
195 | int second = (timeString.charAt(i++) - '0') * 10
196 | + (timeString.charAt(i++) - '0');
197 |
198 | return new TimeOfDay(hour, minute, second);
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/Constants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import java.io.File;
20 |
21 |
22 | /**
23 | * Contains the internal constants that are used in the download manager.
24 | * As a general rule, modifying these constants should be done with care.
25 | */
26 | public class Constants {
27 | /** Tag used for debugging/logging */
28 | public static final String TAG = "LVLDL";
29 |
30 | /**
31 | * Expansion path where we store obb files
32 | */
33 | public static final String EXP_PATH = File.separator + "Android"
34 | + File.separator + "obb" + File.separator;
35 |
36 | /** The intent that gets sent when the service must wake up for a retry */
37 | public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP";
38 |
39 | /** the intent that gets sent when clicking a successful download */
40 | public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN";
41 |
42 | /** the intent that gets sent when clicking an incomplete/failed download */
43 | public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST";
44 |
45 | /** the intent that gets sent when deleting the notification of a completed download */
46 | public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE";
47 |
48 | /**
49 | * When a number has to be appended to the filename, this string is used to separate the
50 | * base filename from the sequence number
51 | */
52 | public static final String FILENAME_SEQUENCE_SEPARATOR = "-";
53 |
54 | /** The default user agent used for downloads */
55 | public static final String DEFAULT_USER_AGENT = "Android.LVLDM";
56 |
57 | /** The buffer size used to stream the data */
58 | public static final int BUFFER_SIZE = 4096;
59 |
60 | /** The minimum amount of progress that has to be done before the progress bar gets updated */
61 | public static final int MIN_PROGRESS_STEP = 4096;
62 |
63 | /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */
64 | public static final long MIN_PROGRESS_TIME = 1000;
65 |
66 | /** The maximum number of rows in the database (FIFO) */
67 | public static final int MAX_DOWNLOADS = 1000;
68 |
69 | /**
70 | * The number of times that the download manager will retry its network
71 | * operations when no progress is happening before it gives up.
72 | */
73 | public static final int MAX_RETRIES = 5;
74 |
75 | /**
76 | * The minimum amount of time that the download manager accepts for
77 | * a Retry-After response header with a parameter in delta-seconds.
78 | */
79 | public static final int MIN_RETRY_AFTER = 30; // 30s
80 |
81 | /**
82 | * The maximum amount of time that the download manager accepts for
83 | * a Retry-After response header with a parameter in delta-seconds.
84 | */
85 | public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h
86 |
87 | /**
88 | * The maximum number of redirects.
89 | */
90 | public static final int MAX_REDIRECTS = 5; // can't be more than 7.
91 |
92 | /**
93 | * The time between a failure and the first retry after an IOException.
94 | * Each subsequent retry grows exponentially, doubling each time.
95 | * The time is in seconds.
96 | */
97 | public static final int RETRY_FIRST_DELAY = 30;
98 |
99 | /** Enable separate connectivity logging */
100 | public static final boolean LOGX = true;
101 |
102 | /** Enable verbose logging */
103 | public static final boolean LOGV = false;
104 |
105 | /** Enable super-verbose logging */
106 | private static final boolean LOCAL_LOGVV = false;
107 | public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
108 |
109 | /**
110 | * This download has successfully completed.
111 | * Warning: there might be other status values that indicate success
112 | * in the future.
113 | * Use isSucccess() to capture the entire category.
114 | */
115 | public static final int STATUS_SUCCESS = 200;
116 |
117 | /**
118 | * This request couldn't be parsed. This is also used when processing
119 | * requests with unknown/unsupported URI schemes.
120 | */
121 | public static final int STATUS_BAD_REQUEST = 400;
122 |
123 | /**
124 | * This download can't be performed because the content type cannot be
125 | * handled.
126 | */
127 | public static final int STATUS_NOT_ACCEPTABLE = 406;
128 |
129 | /**
130 | * This download cannot be performed because the length cannot be
131 | * determined accurately. This is the code for the HTTP error "Length
132 | * Required", which is typically used when making requests that require
133 | * a content length but don't have one, and it is also used in the
134 | * client when a response is received whose length cannot be determined
135 | * accurately (therefore making it impossible to know when a download
136 | * completes).
137 | */
138 | public static final int STATUS_LENGTH_REQUIRED = 411;
139 |
140 | /**
141 | * This download was interrupted and cannot be resumed.
142 | * This is the code for the HTTP error "Precondition Failed", and it is
143 | * also used in situations where the client doesn't have an ETag at all.
144 | */
145 | public static final int STATUS_PRECONDITION_FAILED = 412;
146 |
147 | /**
148 | * The lowest-valued error status that is not an actual HTTP status code.
149 | */
150 | public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
151 |
152 | /**
153 | * The requested destination file already exists.
154 | */
155 | public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
156 |
157 | /**
158 | * Some possibly transient error occurred, but we can't resume the download.
159 | */
160 | public static final int STATUS_CANNOT_RESUME = 489;
161 |
162 | /**
163 | * This download was canceled
164 | */
165 | public static final int STATUS_CANCELED = 490;
166 |
167 | /**
168 | * This download has completed with an error.
169 | * Warning: there will be other status values that indicate errors in
170 | * the future. Use isStatusError() to capture the entire category.
171 | */
172 | public static final int STATUS_UNKNOWN_ERROR = 491;
173 |
174 | /**
175 | * This download couldn't be completed because of a storage issue.
176 | * Typically, that's because the filesystem is missing or full.
177 | * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
178 | * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
179 | */
180 | public static final int STATUS_FILE_ERROR = 492;
181 |
182 | /**
183 | * This download couldn't be completed because of an HTTP
184 | * redirect response that the download manager couldn't
185 | * handle.
186 | */
187 | public static final int STATUS_UNHANDLED_REDIRECT = 493;
188 |
189 | /**
190 | * This download couldn't be completed because of an
191 | * unspecified unhandled HTTP code.
192 | */
193 | public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
194 |
195 | /**
196 | * This download couldn't be completed because of an
197 | * error receiving or processing data at the HTTP level.
198 | */
199 | public static final int STATUS_HTTP_DATA_ERROR = 495;
200 |
201 | /**
202 | * This download couldn't be completed because of an
203 | * HttpException while setting up the request.
204 | */
205 | public static final int STATUS_HTTP_EXCEPTION = 496;
206 |
207 | /**
208 | * This download couldn't be completed because there were
209 | * too many redirects.
210 | */
211 | public static final int STATUS_TOO_MANY_REDIRECTS = 497;
212 |
213 | /**
214 | * This download couldn't be completed due to insufficient storage
215 | * space. Typically, this is because the SD card is full.
216 | */
217 | public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
218 |
219 | /**
220 | * This download couldn't be completed because no external storage
221 | * device was found. Typically, this is because the SD card is not
222 | * mounted.
223 | */
224 | public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
225 |
226 | /**
227 | * The wake duration to check to see if a download is possible.
228 | */
229 | public static final long WATCHDOG_WAKE_TIMER = 60*1000;
230 |
231 | /**
232 | * The wake duration to check to see if the process was killed.
233 | */
234 | public static final long ACTIVE_THREAD_WATCHDOG = 5*1000;
235 |
236 | }
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader.impl;
18 |
19 | import com.android.vending.expansion.downloader.R;
20 | import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
21 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
22 | import com.google.android.vending.expansion.downloader.Helpers;
23 | import com.google.android.vending.expansion.downloader.IDownloaderClient;
24 |
25 | import android.app.NotificationManager;
26 | import android.app.PendingIntent;
27 | import android.content.Context;
28 | import android.os.Build;
29 | import android.os.Messenger;
30 | import android.support.v4.app.NotificationCompat;
31 |
32 | /**
33 | * This class handles displaying the notification associated with the download
34 | * queue going on in the download manager. It handles multiple status types;
35 | * Some require user interaction and some do not. Some of the user interactions
36 | * may be transient. (for example: the user is queried to continue the download
37 | * on 3G when it started on WiFi, but then the phone locks onto WiFi again so
38 | * the prompt automatically goes away)
39 | *
40 | * The application interface for the downloader also needs to understand and
41 | * handle these transient states.
42 | */
43 | public class DownloadNotification implements IDownloaderClient {
44 |
45 | private int mState;
46 | private final Context mContext;
47 | private final NotificationManager mNotificationManager;
48 | private CharSequence mCurrentTitle;
49 |
50 | private IDownloaderClient mClientProxy;
51 | private NotificationCompat.Builder mActiveDownloadBuilder;
52 | private NotificationCompat.Builder mBuilder;
53 | private NotificationCompat.Builder mCurrentBuilder;
54 | private CharSequence mLabel;
55 | private String mCurrentText;
56 | private DownloadProgressInfo mProgressInfo;
57 | private PendingIntent mContentIntent;
58 |
59 | static final String LOGTAG = "DownloadNotification";
60 | static final int NOTIFICATION_ID = LOGTAG.hashCode();
61 |
62 | public PendingIntent getClientIntent() {
63 | return mContentIntent;
64 | }
65 |
66 | public void setClientIntent(PendingIntent clientIntent) {
67 | this.mBuilder.setContentIntent(clientIntent);
68 | this.mActiveDownloadBuilder.setContentIntent(clientIntent);
69 | this.mContentIntent = clientIntent;
70 | }
71 |
72 | public void resendState() {
73 | if (null != mClientProxy) {
74 | mClientProxy.onDownloadStateChanged(mState);
75 | }
76 | }
77 |
78 | @Override
79 | public void onDownloadStateChanged(int newState) {
80 | if (null != mClientProxy) {
81 | mClientProxy.onDownloadStateChanged(newState);
82 | }
83 | if (newState != mState) {
84 | mState = newState;
85 | if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) {
86 | return;
87 | }
88 | int stringDownloadID;
89 | int iconResource;
90 | boolean ongoingEvent;
91 |
92 | // get the new title string and paused text
93 | switch (newState) {
94 | case 0:
95 | iconResource = android.R.drawable.stat_sys_warning;
96 | stringDownloadID = R.string.state_unknown;
97 | ongoingEvent = false;
98 | break;
99 |
100 | case IDownloaderClient.STATE_DOWNLOADING:
101 | iconResource = android.R.drawable.stat_sys_download;
102 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
103 | ongoingEvent = true;
104 | break;
105 |
106 | case IDownloaderClient.STATE_FETCHING_URL:
107 | case IDownloaderClient.STATE_CONNECTING:
108 | iconResource = android.R.drawable.stat_sys_download_done;
109 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
110 | ongoingEvent = true;
111 | break;
112 |
113 | case IDownloaderClient.STATE_COMPLETED:
114 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
115 | iconResource = android.R.drawable.stat_sys_download_done;
116 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
117 | ongoingEvent = false;
118 | break;
119 |
120 | case IDownloaderClient.STATE_FAILED:
121 | case IDownloaderClient.STATE_FAILED_CANCELED:
122 | case IDownloaderClient.STATE_FAILED_FETCHING_URL:
123 | case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
124 | case IDownloaderClient.STATE_FAILED_UNLICENSED:
125 | iconResource = android.R.drawable.stat_sys_warning;
126 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
127 | ongoingEvent = false;
128 | break;
129 |
130 | default:
131 | iconResource = android.R.drawable.stat_sys_warning;
132 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
133 | ongoingEvent = true;
134 | break;
135 | }
136 |
137 | mCurrentText = mContext.getString(stringDownloadID);
138 | mCurrentTitle = mLabel;
139 | mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText);
140 | mCurrentBuilder.setSmallIcon(iconResource);
141 | mCurrentBuilder.setContentTitle(mCurrentTitle);
142 | mCurrentBuilder.setContentText(mCurrentText);
143 | if (ongoingEvent) {
144 | mCurrentBuilder.setOngoing(true);
145 | } else {
146 | mCurrentBuilder.setOngoing(false);
147 | mCurrentBuilder.setAutoCancel(true);
148 | }
149 | mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
150 | }
151 | }
152 |
153 | @Override
154 | public void onDownloadProgress(DownloadProgressInfo progress) {
155 | mProgressInfo = progress;
156 | if (null != mClientProxy) {
157 | mClientProxy.onDownloadProgress(progress);
158 | }
159 | if (progress.mOverallTotal <= 0) {
160 | // we just show the text
161 | mBuilder.setTicker(mCurrentTitle);
162 | mBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
163 | mBuilder.setContentTitle(mCurrentTitle);
164 | mBuilder.setContentText(mCurrentText);
165 | mCurrentBuilder = mBuilder;
166 | } else {
167 | mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false);
168 | mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
169 | mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
170 | mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText);
171 | mActiveDownloadBuilder.setContentTitle(mLabel);
172 | mActiveDownloadBuilder.setContentInfo(mContext.getString(R.string.time_remaining_notification,
173 | Helpers.getTimeRemaining(progress.mTimeRemaining)));
174 | mCurrentBuilder = mActiveDownloadBuilder;
175 | }
176 | mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
177 | }
178 |
179 | /**
180 | * Called in response to onClientUpdated. Creates a new proxy and notifies
181 | * it of the current state.
182 | *
183 | * @param msg the client Messenger to notify
184 | */
185 | public void setMessenger(Messenger msg) {
186 | mClientProxy = DownloaderClientMarshaller.CreateProxy(msg);
187 | if (null != mProgressInfo) {
188 | mClientProxy.onDownloadProgress(mProgressInfo);
189 | }
190 | if (mState != -1) {
191 | mClientProxy.onDownloadStateChanged(mState);
192 | }
193 | }
194 |
195 | /**
196 | * Constructor
197 | *
198 | * @param ctx The context to use to obtain access to the Notification
199 | * Service
200 | */
201 | DownloadNotification(Context ctx, CharSequence applicationLabel) {
202 | mState = -1;
203 | mContext = ctx;
204 | mLabel = applicationLabel;
205 | mNotificationManager = (NotificationManager)
206 | mContext.getSystemService(Context.NOTIFICATION_SERVICE);
207 | mActiveDownloadBuilder = new NotificationCompat.Builder(ctx);
208 | mBuilder = new NotificationCompat.Builder(ctx);
209 |
210 | // Set Notification category and priorities to something that makes sense for a long
211 | // lived background task.
212 | mActiveDownloadBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
213 | mActiveDownloadBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS);
214 |
215 | mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
216 | mBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS);
217 |
218 | mCurrentBuilder = mBuilder;
219 | }
220 |
221 | @Override
222 | public void onServiceConnected(Messenger m) {
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/zip_file/src/com/google/android/vending/expansion/zipfile/APEZProvider.java:
--------------------------------------------------------------------------------
1 |
2 | package com.google.android.vending.expansion.zipfile;
3 |
4 | import android.content.ContentProvider;
5 | import android.content.ContentProviderOperation;
6 | import android.content.ContentProviderResult;
7 | import android.content.ContentValues;
8 | import android.content.Context;
9 | import android.content.OperationApplicationException;
10 | import android.content.pm.PackageInfo;
11 | import android.content.pm.PackageManager;
12 | import android.content.pm.PackageManager.NameNotFoundException;
13 | import android.content.pm.ProviderInfo;
14 | import android.content.res.AssetFileDescriptor;
15 | import android.database.Cursor;
16 | import android.database.MatrixCursor;
17 | import android.net.Uri;
18 | import android.os.ParcelFileDescriptor;
19 | import android.provider.BaseColumns;
20 |
21 | /*
22 | * Copyright (C) 2012 The Android Open Source Project
23 | *
24 | * Licensed under the Apache License, Version 2.0 (the "License");
25 | * you may not use this file except in compliance with the License.
26 | * You may obtain a copy of the License at
27 | *
28 | * http://www.apache.org/licenses/LICENSE-2.0
29 | *
30 | * Unless required by applicable law or agreed to in writing, software
31 | * distributed under the License is distributed on an "AS IS" BASIS,
32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 | * See the License for the specific language governing permissions and
34 | * limitations under the License.
35 | */
36 |
37 | //To implement APEZProvider in your application, you'll want to change
38 | //the AUTHORITY to match what you define in the manifest.
39 |
40 | import com.google.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
41 |
42 | import java.io.FileNotFoundException;
43 | import java.io.IOException;
44 | import java.util.ArrayList;
45 |
46 | /**
47 | * This content provider is an optional part of the library.
48 | *
49 | * Most apps don't need to use this class. This defines a ContentProvider that marshalls the data
50 | * from the ZIP files through a content provider Uri in order to provide file access for certain
51 | * Android APIs that expect Uri access to media files.
52 | */
53 | public abstract class APEZProvider extends ContentProvider {
54 |
55 | private ZipResourceFile mAPKExtensionFile;
56 | private boolean mInit;
57 |
58 | public static final String FILEID = BaseColumns._ID;
59 | public static final String FILENAME = "ZPFN";
60 | public static final String ZIPFILE = "ZFIL";
61 | public static final String MODIFICATION = "ZMOD";
62 | public static final String CRC32 = "ZCRC";
63 | public static final String COMPRESSEDLEN = "ZCOL";
64 | public static final String UNCOMPRESSEDLEN = "ZUNL";
65 | public static final String COMPRESSIONTYPE = "ZTYP";
66 |
67 | public static final String[] ALL_FIELDS = {
68 | FILEID,
69 | FILENAME,
70 | ZIPFILE,
71 | MODIFICATION,
72 | CRC32,
73 | COMPRESSEDLEN,
74 | UNCOMPRESSEDLEN,
75 | COMPRESSIONTYPE
76 | };
77 |
78 | public static final int FILEID_IDX = 0;
79 | public static final int FILENAME_IDX = 1;
80 | public static final int ZIPFILE_IDX = 2;
81 | public static final int MOD_IDX = 3;
82 | public static final int CRC_IDX = 4;
83 | public static final int COMPLEN_IDX = 5;
84 | public static final int UNCOMPLEN_IDX = 6;
85 | public static final int COMPTYPE_IDX = 7;
86 |
87 | public static final int[] ALL_FIELDS_INT = {
88 | FILEID_IDX,
89 | FILENAME_IDX,
90 | ZIPFILE_IDX,
91 | MOD_IDX,
92 | CRC_IDX,
93 | COMPLEN_IDX,
94 | UNCOMPLEN_IDX,
95 | COMPTYPE_IDX
96 | };
97 |
98 | /**
99 | * This needs to match the authority in your manifest
100 | */
101 | public abstract String getAuthority();
102 |
103 | @Override
104 | public int delete(Uri arg0, String arg1, String[] arg2) {
105 | // TODO Auto-generated method stub
106 | return 0;
107 | }
108 |
109 | @Override
110 | public String getType(Uri uri) {
111 | return "vnd.android.cursor.item/asset";
112 | }
113 |
114 | @Override
115 | public Uri insert(Uri uri, ContentValues values) {
116 | // TODO Auto-generated method stub
117 | return null;
118 | }
119 |
120 | static private final String NO_FILE = "N";
121 |
122 | private boolean initIfNecessary() {
123 | if (!mInit) {
124 | Context ctx = getContext();
125 | PackageManager pm = ctx.getPackageManager();
126 | ProviderInfo pi = pm.resolveContentProvider(getAuthority(),
127 | PackageManager.GET_META_DATA);
128 | PackageInfo packInfo;
129 | try {
130 | packInfo = pm.getPackageInfo(ctx.getPackageName(), 0);
131 | } catch (NameNotFoundException e1) {
132 | e1.printStackTrace();
133 | return false;
134 | }
135 | int patchFileVersion;
136 | int mainFileVersion;
137 | int appVersionCode = packInfo.versionCode;
138 | String[] resourceFiles = null;
139 | if (null != pi.metaData) {
140 | mainFileVersion = pi.metaData.getInt("mainVersion", appVersionCode);
141 | patchFileVersion = pi.metaData.getInt("patchVersion", appVersionCode);
142 | String mainFileName = pi.metaData.getString("mainFilename");
143 | if (null != mainFileName) {
144 | String patchFileName = pi.metaData.getString("patchFilename");
145 | if (null != patchFileName) {
146 | resourceFiles = new String[] {
147 | mainFileName, patchFileName
148 | };
149 | } else {
150 | resourceFiles = new String[] {
151 | mainFileName
152 | };
153 | }
154 | }
155 | } else {
156 | mainFileVersion = patchFileVersion = appVersionCode;
157 | }
158 | try {
159 | if (null == resourceFiles) {
160 | mAPKExtensionFile = APKExpansionSupport.getAPKExpansionZipFile(ctx,
161 | mainFileVersion, patchFileVersion);
162 | } else {
163 | mAPKExtensionFile = APKExpansionSupport.getResourceZipFile(resourceFiles);
164 | }
165 | mInit = true;
166 | return true;
167 | } catch (IOException e) {
168 | e.printStackTrace();
169 | }
170 | }
171 | return false;
172 | }
173 |
174 | @Override
175 | public boolean onCreate() {
176 | return true;
177 | }
178 |
179 | @Override
180 | public AssetFileDescriptor openAssetFile(Uri uri, String mode)
181 | throws FileNotFoundException {
182 | initIfNecessary();
183 | String path = uri.getEncodedPath();
184 | if (path.startsWith("/")) {
185 | path = path.substring(1);
186 | }
187 | return mAPKExtensionFile.getAssetFileDescriptor(path);
188 | }
189 |
190 | @Override
191 | public ContentProviderResult[] applyBatch(
192 | ArrayList operations)
193 | throws OperationApplicationException {
194 | initIfNecessary();
195 | return super.applyBatch(operations);
196 | }
197 |
198 | @Override
199 | public ParcelFileDescriptor openFile(Uri uri, String mode)
200 | throws FileNotFoundException {
201 | initIfNecessary();
202 | AssetFileDescriptor af = openAssetFile(uri, mode);
203 | if (null != af) {
204 | return af.getParcelFileDescriptor();
205 | }
206 | return null;
207 | }
208 |
209 | @Override
210 | public Cursor query(Uri uri, String[] projection, String selection,
211 | String[] selectionArgs, String sortOrder) {
212 | initIfNecessary();
213 | // lists all of the items in the file that match
214 | ZipEntryRO[] zipEntries;
215 | if (null == mAPKExtensionFile) {
216 | zipEntries = new ZipEntryRO[0];
217 | } else {
218 | zipEntries = mAPKExtensionFile.getAllEntries();
219 | }
220 | int[] intProjection;
221 | if (null == projection) {
222 | intProjection = ALL_FIELDS_INT;
223 | projection = ALL_FIELDS;
224 | } else {
225 | int len = projection.length;
226 | intProjection = new int[len];
227 | for (int i = 0; i < len; i++) {
228 | if (projection[i].equals(FILEID)) {
229 | intProjection[i] = FILEID_IDX;
230 | } else if (projection[i].equals(FILENAME)) {
231 | intProjection[i] = FILENAME_IDX;
232 | } else if (projection[i].equals(ZIPFILE)) {
233 | intProjection[i] = ZIPFILE_IDX;
234 | } else if (projection[i].equals(MODIFICATION)) {
235 | intProjection[i] = MOD_IDX;
236 | } else if (projection[i].equals(CRC32)) {
237 | intProjection[i] = CRC_IDX;
238 | } else if (projection[i].equals(COMPRESSEDLEN)) {
239 | intProjection[i] = COMPLEN_IDX;
240 | } else if (projection[i].equals(UNCOMPRESSEDLEN)) {
241 | intProjection[i] = UNCOMPLEN_IDX;
242 | } else if (projection[i].equals(COMPRESSIONTYPE)) {
243 | intProjection[i] = COMPTYPE_IDX;
244 | } else {
245 | throw new RuntimeException();
246 | }
247 | }
248 | }
249 | MatrixCursor mc = new MatrixCursor(projection, zipEntries.length);
250 | int len = intProjection.length;
251 | for (ZipEntryRO zer : zipEntries) {
252 | MatrixCursor.RowBuilder rb = mc.newRow();
253 | for (int i = 0; i < len; i++) {
254 | switch (intProjection[i]) {
255 | case FILEID_IDX:
256 | rb.add(i);
257 | break;
258 | case FILENAME_IDX:
259 | rb.add(zer.mFileName);
260 | break;
261 | case ZIPFILE_IDX:
262 | rb.add(zer.getZipFileName());
263 | break;
264 | case MOD_IDX:
265 | rb.add(zer.mWhenModified);
266 | break;
267 | case CRC_IDX:
268 | rb.add(zer.mCRC32);
269 | break;
270 | case COMPLEN_IDX:
271 | rb.add(zer.mCompressedLength);
272 | break;
273 | case UNCOMPLEN_IDX:
274 | rb.add(zer.mUncompressedLength);
275 | break;
276 | case COMPTYPE_IDX:
277 | rb.add(zer.mMethod);
278 | break;
279 | }
280 | }
281 | }
282 | return mc;
283 | }
284 |
285 | @Override
286 | public int update(Uri uri, ContentValues values, String selection,
287 | String[] selectionArgs) {
288 | return 0;
289 | }
290 |
291 | }
292 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService;
20 |
21 | import android.app.PendingIntent;
22 | import android.content.ComponentName;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.ServiceConnection;
26 | import android.content.pm.PackageManager.NameNotFoundException;
27 | import android.os.Bundle;
28 | import android.os.Handler;
29 | import android.os.IBinder;
30 | import android.os.Message;
31 | import android.os.Messenger;
32 | import android.os.RemoteException;
33 | import android.util.Log;
34 |
35 |
36 |
37 | /**
38 | * This class binds the service API to your application client. It contains the IDownloaderClient proxy,
39 | * which is used to call functions in your client as well as the Stub, which is used to call functions
40 | * in the client implementation of IDownloaderClient.
41 | *
42 | *
The IPC is implemented using an Android Messenger and a service Binder. The connect method
43 | * should be called whenever the client wants to bind to the service. It opens up a service connection
44 | * that ends up calling the onServiceConnected client API that passes the service messenger
45 | * in. If the client wants to be notified by the service, it is responsible for then passing its
46 | * messenger to the service in a separate call.
47 | *
48 | *
Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}.
49 | *
50 | *
When your application first starts, you should first check whether your app's expansion files are
51 | * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which
52 | * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method
53 | * returns a value indicating whether download is required or not.
54 | *
55 | *
If a download is required, {@link #startDownloadServiceIfRequired} begins the download through
56 | * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link
57 | * IStub} object that you need in order to receive calls through your {@link IDownloaderClient}
58 | * interface.
59 | */
60 | public class DownloaderClientMarshaller {
61 | public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10;
62 | public static final int MSG_ONDOWNLOADPROGRESS = 11;
63 | public static final int MSG_ONSERVICECONNECTED = 12;
64 |
65 | public static final String PARAM_NEW_STATE = "newState";
66 | public static final String PARAM_PROGRESS = "progress";
67 | public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;
68 |
69 | public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED;
70 | public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED;
71 | public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED;
72 |
73 | private static class Proxy implements IDownloaderClient {
74 | private Messenger mServiceMessenger;
75 |
76 | @Override
77 | public void onDownloadStateChanged(int newState) {
78 | Bundle params = new Bundle(1);
79 | params.putInt(PARAM_NEW_STATE, newState);
80 | send(MSG_ONDOWNLOADSTATE_CHANGED, params);
81 | }
82 |
83 | @Override
84 | public void onDownloadProgress(DownloadProgressInfo progress) {
85 | Bundle params = new Bundle(1);
86 | params.putParcelable(PARAM_PROGRESS, progress);
87 | send(MSG_ONDOWNLOADPROGRESS, params);
88 | }
89 |
90 | private void send(int method, Bundle params) {
91 | Message m = Message.obtain(null, method);
92 | m.setData(params);
93 | try {
94 | mServiceMessenger.send(m);
95 | } catch (RemoteException e) {
96 | e.printStackTrace();
97 | }
98 | }
99 |
100 | public Proxy(Messenger msg) {
101 | mServiceMessenger = msg;
102 | }
103 |
104 | @Override
105 | public void onServiceConnected(Messenger m) {
106 | /**
107 | * This is never called through the proxy.
108 | */
109 | }
110 | }
111 |
112 | private static class Stub implements IStub {
113 | private IDownloaderClient mItf = null;
114 | private Class> mDownloaderServiceClass;
115 | private boolean mBound;
116 | private Messenger mServiceMessenger;
117 | private Context mContext;
118 | /**
119 | * Target we publish for clients to send messages to IncomingHandler.
120 | */
121 | final Messenger mMessenger = new Messenger(new Handler() {
122 | @Override
123 | public void handleMessage(Message msg) {
124 | switch (msg.what) {
125 | case MSG_ONDOWNLOADPROGRESS:
126 | Bundle bun = msg.getData();
127 | if ( null != mContext ) {
128 | bun.setClassLoader(mContext.getClassLoader());
129 | DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
130 | .getParcelable(PARAM_PROGRESS);
131 | mItf.onDownloadProgress(dpi);
132 | }
133 | break;
134 | case MSG_ONDOWNLOADSTATE_CHANGED:
135 | mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
136 | break;
137 | case MSG_ONSERVICECONNECTED:
138 | mItf.onServiceConnected(
139 | (Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
140 | break;
141 | }
142 | }
143 | });
144 |
145 | public Stub(IDownloaderClient itf, Class> downloaderService) {
146 | mItf = itf;
147 | mDownloaderServiceClass = downloaderService;
148 | }
149 |
150 | /**
151 | * Class for interacting with the main interface of the service.
152 | */
153 | private ServiceConnection mConnection = new ServiceConnection() {
154 | public void onServiceConnected(ComponentName className, IBinder service) {
155 | // This is called when the connection with the service has been
156 | // established, giving us the object we can use to
157 | // interact with the service. We are communicating with the
158 | // service using a Messenger, so here we get a client-side
159 | // representation of that from the raw IBinder object.
160 | mServiceMessenger = new Messenger(service);
161 | mItf.onServiceConnected(
162 | mServiceMessenger);
163 | }
164 |
165 | public void onServiceDisconnected(ComponentName className) {
166 | // This is called when the connection with the service has been
167 | // unexpectedly disconnected -- that is, its process crashed.
168 | mServiceMessenger = null;
169 | }
170 | };
171 |
172 | @Override
173 | public void connect(Context c) {
174 | mContext = c;
175 | Intent bindIntent = new Intent(c, mDownloaderServiceClass);
176 | bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
177 | if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
178 | if ( Constants.LOGVV ) {
179 | Log.d(Constants.TAG, "Service Unbound");
180 | }
181 | } else {
182 | mBound = true;
183 | }
184 |
185 | }
186 |
187 | @Override
188 | public void disconnect(Context c) {
189 | if (mBound) {
190 | c.unbindService(mConnection);
191 | mBound = false;
192 | }
193 | mContext = null;
194 | }
195 |
196 | @Override
197 | public Messenger getMessenger() {
198 | return mMessenger;
199 | }
200 | }
201 |
202 | /**
203 | * Returns a proxy that will marshal calls to IDownloaderClient methods
204 | *
205 | * @param msg
206 | * @return
207 | */
208 | public static IDownloaderClient CreateProxy(Messenger msg) {
209 | return new Proxy(msg);
210 | }
211 |
212 | /**
213 | * Returns a stub object that, when connected, will listen for marshaled
214 | * {@link IDownloaderClient} methods and translate them into calls to the supplied
215 | * interface.
216 | *
217 | * @param itf An implementation of IDownloaderClient that will be called
218 | * when remote method calls are unmarshaled.
219 | * @param downloaderService The class for your implementation of {@link
220 | * impl.DownloaderService}.
221 | * @return The {@link IStub} that allows you to connect to the service such that
222 | * your {@link IDownloaderClient} receives status updates.
223 | */
224 | public static IStub CreateStub(IDownloaderClient itf, Class> downloaderService) {
225 | return new Stub(itf, downloaderService);
226 | }
227 |
228 | /**
229 | * Starts the download if necessary. This function starts a flow that does `
230 | * many things. 1) Checks to see if the APK version has been checked and
231 | * the metadata database updated 2) If the APK version does not match,
232 | * checks the new LVL status to see if a new download is required 3) If the
233 | * APK version does match, then checks to see if the download(s) have been
234 | * completed 4) If the downloads have been completed, returns
235 | * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the
236 | * startup of an application to quickly ascertain if the application needs
237 | * to wait to hear about any updated APK expansion files. Note that this does
238 | * mean that the application MUST be run for the first time with a network
239 | * connection, even if Market delivers all of the files.
240 | *
241 | * @param context Your application Context.
242 | * @param notificationClient A PendingIntent to start the Activity in your application
243 | * that shows the download progress and which will also start the application when download
244 | * completes.
245 | * @param serviceClass the class of your {@link imp.DownloaderService} implementation
246 | * @return whether the service was started and the reason for starting the service.
247 | * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link
248 | * #DOWNLOAD_REQUIRED}.
249 | * @throws NameNotFoundException
250 | */
251 | public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient,
252 | Class> serviceClass)
253 | throws NameNotFoundException {
254 | return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
255 | serviceClass);
256 | }
257 |
258 | /**
259 | * This version assumes that the intent contains the pending intent as a parameter. This
260 | * is used for responding to alarms.
261 | *
The pending intent must be in an extra with the key {@link
262 | * impl.DownloaderService#EXTRA_PENDING_INTENT}.
263 | *
264 | * @param context
265 | * @param notificationClient
266 | * @param serviceClass the class of the service to start
267 | * @return
268 | * @throws NameNotFoundException
269 | */
270 | public static int startDownloadServiceIfRequired(Context context, Intent notificationClient,
271 | Class> serviceClass)
272 | throws NameNotFoundException {
273 | return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
274 | serviceClass);
275 | }
276 |
277 | }
278 |
--------------------------------------------------------------------------------
/apkx_sample/src/com/example/google/play/apkx/SampleVideoPlayerActivity.java:
--------------------------------------------------------------------------------
1 |
2 | package com.example.google.play.apkx;
3 |
4 | /*
5 | * Copyright (C) 2012 The Android Open Source Project
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * limitations under the License.
17 | */
18 |
19 | import android.app.Activity;
20 | import android.content.Intent;
21 | import android.media.MediaPlayer;
22 | import android.media.MediaPlayer.OnPreparedListener;
23 | import android.net.Uri;
24 | import android.os.Build;
25 | import android.os.Bundle;
26 | import android.os.Handler;
27 | import android.os.Message;
28 | import android.util.Log;
29 | import android.view.KeyEvent;
30 | import android.view.View;
31 | import android.widget.MediaController;
32 | import android.widget.TextView;
33 | import android.widget.VideoView;
34 |
35 | import java.lang.reflect.InvocationHandler;
36 | import java.lang.reflect.InvocationTargetException;
37 | import java.lang.reflect.Method;
38 | import java.lang.reflect.Proxy;
39 |
40 | /**
41 | * Unlike most of the rest of this example, this class is only really guaranteed
42 | * to work well on Froyo devices or above. This class tries to do the right
43 | * thing with a video player that is constrained to landscape even if its parent
44 | * is not so constrained. This can lead to some interesting state transitions in
45 | * Android.
46 | */
47 | public class SampleVideoPlayerActivity extends Activity implements OnPreparedListener {
48 |
49 | private Uri mMediaUri;
50 | private String mMediaTitle;
51 | private VideoView mVideoView;
52 | private View mPlayerLayout;
53 | private Method mSetSystemUiVisibility;
54 | private Method mSetSystemUiVisibilityListener;
55 | private int mSavedPosition;
56 | private boolean mIgnoreSystemUIChange;
57 | private boolean mResumeVideoPlaybackOnResume;
58 | private boolean mStartVideoOnCreate;
59 | private boolean mHasFocus;
60 | private boolean mSystemKeysShowing;
61 | private Handler mHandler;
62 | private MediaController mMediaController;
63 |
64 | static final private String SAVE_VIDEO_POSITION = "POS";
65 | static final private String RESUME_VIDEO_ON_RESUME = "RES";
66 | protected static final int MSG_HIDE_SYSTEM_UI = 1;
67 | static final private String LOG_TAG = "SampleVideo";
68 |
69 | public void onCreate(Bundle savedInstanceState) {
70 | super.onCreate(savedInstanceState);
71 |
72 | /**
73 | * We create a handler here so that we can delay hiding the system Ui
74 | * for a fraction of a second.
75 | */
76 | mHandler = new Handler() {
77 |
78 | @Override
79 | public void handleMessage(Message msg) {
80 | switch (msg.what) {
81 | case MSG_HIDE_SYSTEM_UI:
82 | hideSystemUi();
83 | break;
84 | }
85 | super.handleMessage(msg);
86 | }
87 |
88 | };
89 |
90 | /**
91 | * We use a combination of getLastNonConfigurationInstance and saved
92 | * instance state to make sure we resume under a variety of cases.
93 | */
94 | if (null == savedInstanceState) {
95 | savedInstanceState = (Bundle) getLastNonConfigurationInstance();
96 | }
97 | if (null != savedInstanceState) {
98 | mResumeVideoPlaybackOnResume = savedInstanceState.getBoolean(RESUME_VIDEO_ON_RESUME,
99 | false);
100 | mStartVideoOnCreate = false;
101 | } else {
102 | mStartVideoOnCreate = true;
103 | mSavedPosition = -1;
104 | }
105 | setContentView(R.layout.videoplayer);
106 |
107 | mPlayerLayout = this.findViewById(R.id.PlayerLayout);
108 |
109 | mMediaUri = this.getIntent().getData();
110 | mMediaTitle = this.getIntent().getStringExtra(Intent.EXTRA_TITLE);
111 | ((TextView) findViewById(R.id.NowPlaying)).setText(mMediaTitle);
112 | mVideoView = (VideoView) findViewById(R.id.VideoView);
113 |
114 | /**
115 | * This code will hide the system UI when the Media Controller is hidden
116 | * on ICS devices.
117 | */
118 | mMediaController = new MediaController(this) {
119 |
120 | /**
121 | * Whenever we hide the MediaController, we also hide the system UI
122 | * a fraction of a second later.
123 | */
124 | @Override
125 | public void hide() {
126 | super.hide();
127 | postHideSystemUi();
128 | }
129 |
130 | /**
131 | * If we are on an ICS device, we don't want the back key to just
132 | * dismiss the media controller, because that will also hide the
133 | * system keys, so the user effectively cannot back out of the app
134 | * using the back key. Since the media controller times out (or
135 | * exits when the user taps on a part of the screen that is not the
136 | * media controller) This seems to be a reasonable compromise
137 | */
138 | @Override
139 | public boolean dispatchKeyEvent(KeyEvent event) {
140 | if (mSystemKeysShowing && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
141 | finish();
142 | }
143 | return super.dispatchKeyEvent(event);
144 | }
145 |
146 | };
147 |
148 | /**
149 | * Set up the use of reflection to hide system UI and use a Proxy to get
150 | * notified when the visibility changes. Once we are playing the video,
151 | * we hide the system UI.
152 | */
153 | try {
154 | mSetSystemUiVisibility = View.class.getMethod("setSystemUiVisibility", int.class);
155 | Class> onChangeListener = Class
156 | .forName("android.view.View$OnSystemUiVisibilityChangeListener");
157 | mSetSystemUiVisibilityListener = View.class.getMethod(
158 | "setOnSystemUiVisibilityChangeListener", onChangeListener);
159 | ClassLoader cl = onChangeListener.getClassLoader();
160 | InvocationHandler h = new InvocationHandler() {
161 |
162 | /**
163 | * This gets called when the view calls the
164 | * OnSystemUiVisibilityChangeListener. Since there is only one
165 | * method in this class, we don't bother to look at the Method
166 | * parameter.
167 | */
168 | @Override
169 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
170 | if (mIgnoreSystemUIChange) {
171 | mIgnoreSystemUIChange = false;
172 | return null;
173 | }
174 | switch (((Integer) args[0]).intValue()) {
175 | case View.SYSTEM_UI_FLAG_LOW_PROFILE:
176 | mMediaController.show();
177 | mSystemKeysShowing = true;
178 | break;
179 | case View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LOW_PROFILE:
180 | case View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:
181 | mSystemKeysShowing = false;
182 | break;
183 | }
184 | return null;
185 | }
186 | };
187 |
188 | Object o = Proxy.newProxyInstance(cl, new Class[] {
189 | onChangeListener
190 | }, h);
191 | mSetSystemUiVisibilityListener.invoke(mPlayerLayout, o);
192 | } catch (NoSuchMethodException e) {
193 | e.printStackTrace();
194 | } catch (ClassNotFoundException e) {
195 | e.printStackTrace();
196 | } catch (IllegalArgumentException e) {
197 | e.printStackTrace();
198 | } catch (IllegalAccessException e) {
199 | e.printStackTrace();
200 | } catch (InvocationTargetException e) {
201 | e.printStackTrace();
202 | }
203 |
204 | mVideoView.setVideoURI(mMediaUri);
205 |
206 | mVideoView.setMediaController(mMediaController);
207 | mVideoView.requestFocus();
208 | mVideoView.setOnPreparedListener(this);
209 |
210 | hideSystemUi();
211 | }
212 |
213 | /**
214 | * We both resume and start the video, depending on the state.
215 | */
216 | private void resumeVideoIfNecessary() {
217 | if (mResumeVideoPlaybackOnResume && mHasFocus) {
218 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
219 | mVideoView.resume();
220 | }
221 | if (!mVideoView.isPlaying()) {
222 | mVideoView.start();
223 | }
224 | mResumeVideoPlaybackOnResume = false;
225 | }
226 | }
227 |
228 | @Override
229 | public void onWindowFocusChanged(boolean hasFocus) {
230 | mHasFocus = hasFocus;
231 | resumeVideoIfNecessary();
232 | super.onWindowFocusChanged(hasFocus);
233 | }
234 |
235 | @Override
236 | protected void onPause() {
237 | if (mHasFocus) {
238 | mResumeVideoPlaybackOnResume = mVideoView.isPlaying();
239 | }
240 | int savedPosition = mVideoView.getCurrentPosition();
241 | // if not active, video view will return 0 for the saved position
242 | if (0 != savedPosition) {
243 | mSavedPosition = savedPosition;
244 | }
245 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
246 | mVideoView.suspend();
247 | }
248 | super.onPause();
249 | }
250 |
251 | @Override
252 | public Object onRetainNonConfigurationInstance() {
253 | Bundle outState = new Bundle();
254 | saveInstance(outState);
255 | return super.onRetainNonConfigurationInstance();
256 | }
257 |
258 | private void saveInstance(Bundle outState) {
259 | int videoPosition = mVideoView.getCurrentPosition();
260 | if (-1 == mSavedPosition) {
261 | mSavedPosition = videoPosition;
262 | }
263 | outState.putInt(SAVE_VIDEO_POSITION, mSavedPosition);
264 | outState.putBoolean(RESUME_VIDEO_ON_RESUME, mResumeVideoPlaybackOnResume);
265 | }
266 |
267 | @Override
268 | protected void onSaveInstanceState(Bundle outState) {
269 | saveInstance(outState);
270 | super.onSaveInstanceState(outState);
271 | }
272 |
273 | @Override
274 | protected void onResume() {
275 | resumeVideoIfNecessary();
276 | super.onResume();
277 | }
278 |
279 | @Override
280 | protected void onDestroy() {
281 | super.onDestroy();
282 | mHandler = null;
283 | mMediaController = null;
284 | if (null != mSetSystemUiVisibilityListener) {
285 | try {
286 | mSetSystemUiVisibilityListener.invoke(mPlayerLayout, new Object[] {
287 | null
288 | });
289 | } catch (Exception e) {
290 | // TODO Auto-generated catch block
291 | e.printStackTrace();
292 | }
293 | }
294 | }
295 |
296 | /**
297 | * We want to avoid a race condition where the system UI doesn't end up
298 | * getting hidden because the touch event gets there before the call to hide
299 | * the UI gets there.
300 | */
301 | private void postHideSystemUi() {
302 | if (null != mSetSystemUiVisibility && null != mHandler) {
303 | mHandler.sendEmptyMessageDelayed(MSG_HIDE_SYSTEM_UI, 100);
304 | }
305 | }
306 |
307 | /**
308 | * Use reflection to hide the system UI.
309 | */
310 | private void hideSystemUi() {
311 | Log.d(LOG_TAG, "Hiding System Ui");
312 | if (null != mSetSystemUiVisibility) {
313 | try {
314 | mSetSystemUiVisibility.invoke(mPlayerLayout, View.SYSTEM_UI_FLAG_LOW_PROFILE
315 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
316 | mIgnoreSystemUIChange = true;
317 | } catch (Exception e) {
318 | e.printStackTrace();
319 | }
320 | }
321 | }
322 |
323 | /**
324 | * If we are being restarted from a paused state, don't start playing.
325 | */
326 | public void onPrepared(MediaPlayer mp) {
327 | mp.start();
328 | if (!mStartVideoOnCreate) {
329 | mp.pause();
330 | }
331 | if (-1 != mSavedPosition) {
332 | mp.seekTo(mSavedPosition);
333 | mSavedPosition = -1;
334 | }
335 | hideSystemUi();
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/Helpers.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader;
18 |
19 | import android.annotation.TargetApi;
20 | import android.content.Context;
21 | import android.os.Build;
22 | import android.os.Environment;
23 | import android.os.StatFs;
24 | import android.os.SystemClock;
25 | import android.util.Log;
26 |
27 | import com.android.vending.expansion.downloader.R;
28 |
29 | import java.io.File;
30 | import java.text.SimpleDateFormat;
31 | import java.util.Date;
32 | import java.util.Locale;
33 | import java.util.Random;
34 | import java.util.TimeZone;
35 | import java.util.regex.Matcher;
36 | import java.util.regex.Pattern;
37 |
38 | /**
39 | * Some helper functions for the download manager
40 | */
41 | public class Helpers {
42 |
43 | public static Random sRandom = new Random(SystemClock.uptimeMillis());
44 |
45 | /** Regex used to parse content-disposition headers */
46 | private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern
47 | .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
48 |
49 | private Helpers() {
50 | }
51 |
52 | /*
53 | * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
54 | * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
55 | * content that is going to be downloaded to the file system. We only support the attachment
56 | * type.
57 | */
58 | static String parseContentDisposition(String contentDisposition) {
59 | try {
60 | Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
61 | if (m.find()) {
62 | return m.group(1);
63 | }
64 | } catch (IllegalStateException ex) {
65 | // This function is defined as returning null when it can't parse
66 | // the header
67 | }
68 | return null;
69 | }
70 |
71 | /**
72 | * @return the root of the filesystem containing the given path
73 | */
74 | public static File getFilesystemRoot(String path) {
75 | File cache = Environment.getDownloadCacheDirectory();
76 | if (path.startsWith(cache.getPath())) {
77 | return cache;
78 | }
79 | File external = Environment.getExternalStorageDirectory();
80 | if (path.startsWith(external.getPath())) {
81 | return external;
82 | }
83 | throw new IllegalArgumentException(
84 | "Cannot determine filesystem root for " + path);
85 | }
86 |
87 | public static boolean isExternalMediaMounted() {
88 | if (!Environment.getExternalStorageState().equals(
89 | Environment.MEDIA_MOUNTED)) {
90 | // No SD card found.
91 | if (Constants.LOGVV) {
92 | Log.d(Constants.TAG, "no external storage");
93 | }
94 | return false;
95 | }
96 | return true;
97 | }
98 |
99 | /**
100 | * @return the number of bytes available on the filesystem rooted at the given File
101 | */
102 | public static long getAvailableBytes(File root) {
103 | StatFs stat = new StatFs(root.getPath());
104 | // put a bit of margin (in case creating the file grows the system by a
105 | // few blocks)
106 | long availableBlocks = (long) stat.getAvailableBlocks() - 4;
107 | return stat.getBlockSize() * availableBlocks;
108 | }
109 |
110 | /**
111 | * Checks whether the filename looks legitimate
112 | */
113 | public static boolean isFilenameValid(String filename) {
114 | filename = filename.replaceFirst("/+", "/"); // normalize leading
115 | // slashes
116 | return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
117 | || filename.startsWith(Environment.getExternalStorageDirectory().toString());
118 | }
119 |
120 | /*
121 | * Delete the given file from device
122 | */
123 | /* package */static void deleteFile(String path) {
124 | try {
125 | File file = new File(path);
126 | file.delete();
127 | } catch (Exception e) {
128 | Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e);
129 | }
130 | }
131 |
132 | /**
133 | * Showing progress in MB here. It would be nice to choose the unit (KB, MB, GB) based on total
134 | * file size, but given what we know about the expected ranges of file sizes for APK expansion
135 | * files, it's probably not necessary.
136 | *
137 | * @param overallProgress
138 | * @param overallTotal
139 | * @return
140 | */
141 |
142 | static public String getDownloadProgressString(long overallProgress, long overallTotal) {
143 | if (overallTotal == 0) {
144 | if (Constants.LOGVV) {
145 | Log.e(Constants.TAG, "Notification called when total is zero");
146 | }
147 | return "";
148 | }
149 | return String.format("%.2f",
150 | (float) overallProgress / (1024.0f * 1024.0f))
151 | + "MB /" +
152 | String.format("%.2f", (float) overallTotal /
153 | (1024.0f * 1024.0f))
154 | + "MB";
155 | }
156 |
157 | /**
158 | * Adds a percentile to getDownloadProgressString.
159 | *
160 | * @param overallProgress
161 | * @param overallTotal
162 | * @return
163 | */
164 | static public String getDownloadProgressStringNotification(long overallProgress,
165 | long overallTotal) {
166 | if (overallTotal == 0) {
167 | if (Constants.LOGVV) {
168 | Log.e(Constants.TAG, "Notification called when total is zero");
169 | }
170 | return "";
171 | }
172 | return getDownloadProgressString(overallProgress, overallTotal) + " (" +
173 | getDownloadProgressPercent(overallProgress, overallTotal) + ")";
174 | }
175 |
176 | public static String getDownloadProgressPercent(long overallProgress, long overallTotal) {
177 | if (overallTotal == 0) {
178 | if (Constants.LOGVV) {
179 | Log.e(Constants.TAG, "Notification called when total is zero");
180 | }
181 | return "";
182 | }
183 | return Long.toString(overallProgress * 100 / overallTotal) + "%";
184 | }
185 |
186 | public static String getSpeedString(float bytesPerMillisecond) {
187 | return String.format("%.2f", bytesPerMillisecond * 1000 / 1024);
188 | }
189 |
190 | public static String getTimeRemaining(long durationInMilliseconds) {
191 | SimpleDateFormat sdf;
192 | if (durationInMilliseconds > 1000 * 60 * 60) {
193 | sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
194 | } else {
195 | sdf = new SimpleDateFormat("mm:ss", Locale.getDefault());
196 | }
197 | return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset()));
198 | }
199 |
200 | /**
201 | * Returns the file name (without full path) for an Expansion APK file from the given context.
202 | *
203 | * @param c the context
204 | * @param mainFile true for main file, false for patch file
205 | * @param versionCode the version of the file
206 | * @return String the file name of the expansion file
207 | */
208 | public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) {
209 | return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb";
210 | }
211 |
212 | /**
213 | * Returns the filename (where the file should be saved) from info about a download
214 | */
215 | static public String generateSaveFileName(Context c, String fileName) {
216 | String path = getSaveFilePath(c)
217 | + File.separator + fileName;
218 | return path;
219 | }
220 |
221 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
222 | static public String getSaveFilePath(Context c) {
223 | // This technically existed since Honeycomb, but it is critical
224 | // on KitKat and greater versions since it will create the
225 | // directory if needed
226 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
227 | return c.getObbDir().toString();
228 | } else {
229 | File root = Environment.getExternalStorageDirectory();
230 | String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
231 | return path;
232 | }
233 | }
234 |
235 | /**
236 | * Helper function to ascertain the existence of a file and return true/false appropriately
237 | *
238 | * @param c the app/activity/service context
239 | * @param fileName the name (sans path) of the file to query
240 | * @param fileSize the size that the file must match
241 | * @param deleteFileOnMismatch if the file sizes do not match, delete the file
242 | * @return true if it does exist, false otherwise
243 | */
244 | static public boolean doesFileExist(Context c, String fileName, long fileSize,
245 | boolean deleteFileOnMismatch) {
246 | // the file may have been delivered by Play --- let's make sure
247 | // it's the size we expect
248 | File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
249 | if (fileForNewFile.exists()) {
250 | if (fileForNewFile.length() == fileSize) {
251 | return true;
252 | }
253 | if (deleteFileOnMismatch) {
254 | // delete the file --- we won't be able to resume
255 | // because we cannot confirm the integrity of the file
256 | fileForNewFile.delete();
257 | }
258 | }
259 | return false;
260 | }
261 |
262 | public static final int FS_READABLE = 0;
263 | public static final int FS_DOES_NOT_EXIST = 1;
264 | public static final int FS_CANNOT_READ = 2;
265 |
266 | /**
267 | * Helper function to ascertain whether a file can be read.
268 | *
269 | * @param c the app/activity/service context
270 | * @param fileName the name (sans path) of the file to query
271 | * @return true if it does exist, false otherwise
272 | */
273 | static public int getFileStatus(Context c, String fileName) {
274 | // the file may have been delivered by Play --- let's make sure
275 | // it's the size we expect
276 | File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
277 | int returnValue;
278 | if (fileForNewFile.exists()) {
279 | if (fileForNewFile.canRead()) {
280 | returnValue = FS_READABLE;
281 | } else {
282 | returnValue = FS_CANNOT_READ;
283 | }
284 | } else {
285 | returnValue = FS_DOES_NOT_EXIST;
286 | }
287 | return returnValue;
288 | }
289 |
290 | /**
291 | * Helper function to ascertain whether the application has the correct access to the OBB
292 | * directory to allow an OBB file to be written.
293 | *
294 | * @param c the app/activity/service context
295 | * @return true if the application can write an OBB file, false otherwise
296 | */
297 | static public boolean canWriteOBBFile(Context c) {
298 | String path = getSaveFilePath(c);
299 | File fileForNewFile = new File(path);
300 | boolean canWrite;
301 | if (fileForNewFile.exists()) {
302 | canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite();
303 | } else {
304 | canWrite = fileForNewFile.mkdirs();
305 | }
306 | return canWrite;
307 | }
308 |
309 | /**
310 | * Converts download states that are returned by the
311 | * {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful
312 | * if using the state strings built into the library to display user messages.
313 | *
314 | * @param state One of the STATE_* constants from {@link IDownloaderClient}.
315 | * @return string resource ID for the corresponding string.
316 | */
317 | static public int getDownloaderStringResourceIDFromState(int state) {
318 | switch (state) {
319 | case IDownloaderClient.STATE_IDLE:
320 | return R.string.state_idle;
321 | case IDownloaderClient.STATE_FETCHING_URL:
322 | return R.string.state_fetching_url;
323 | case IDownloaderClient.STATE_CONNECTING:
324 | return R.string.state_connecting;
325 | case IDownloaderClient.STATE_DOWNLOADING:
326 | return R.string.state_downloading;
327 | case IDownloaderClient.STATE_COMPLETED:
328 | return R.string.state_completed;
329 | case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
330 | return R.string.state_paused_network_unavailable;
331 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
332 | return R.string.state_paused_by_request;
333 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
334 | return R.string.state_paused_wifi_disabled;
335 | case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
336 | return R.string.state_paused_wifi_unavailable;
337 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
338 | return R.string.state_paused_wifi_disabled;
339 | case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
340 | return R.string.state_paused_wifi_unavailable;
341 | case IDownloaderClient.STATE_PAUSED_ROAMING:
342 | return R.string.state_paused_roaming;
343 | case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
344 | return R.string.state_paused_network_setup_failure;
345 | case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
346 | return R.string.state_paused_sdcard_unavailable;
347 | case IDownloaderClient.STATE_FAILED_UNLICENSED:
348 | return R.string.state_failed_unlicensed;
349 | case IDownloaderClient.STATE_FAILED_FETCHING_URL:
350 | return R.string.state_failed_fetching_url;
351 | case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
352 | return R.string.state_failed_sdcard_full;
353 | case IDownloaderClient.STATE_FAILED_CANCELED:
354 | return R.string.state_failed_cancelled;
355 | default:
356 | return R.string.state_unknown;
357 | }
358 | }
359 |
360 | }
361 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 | To get started with the Android SDK, you must agree to the following terms and conditions.
2 |
3 | This is the Android SDK License Agreement (the "License Agreement").
4 |
5 | 1. Introduction
6 |
7 | 1.1 The Android SDK (referred to in the License Agreement as the "SDK" and specifically including the Android system files, packaged APIs, and SDK library files and tools , if and when they are made available) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the SDK.
8 |
9 | 1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time.
10 |
11 | 1.3 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
12 |
13 | 2. Accepting the License Agreement
14 |
15 | 2.1 In order to use the SDK, you must first agree to the License Agreement. You may not use the SDK if you do not accept the License Agreement.
16 |
17 | 2.2 By clicking to accept and/or using the SDK, you hereby agree to the terms of the License Agreement.
18 |
19 | 2.3 You may not use the SDK and may not accept the License Agreement if you are a person barred from receiving the SDK under the laws of the United States or other countries including the country in which you are resident or from which you use the SDK.
20 |
21 | 2.4 If you will use the SDK internally within your company or organization you agree to be bound by the License Agreement on behalf of your employer or other entity, and you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the SDK on behalf of your employer or other entity.
22 |
23 | 3. SDK License from Google
24 |
25 | 3.1 Subject to the terms of the License Agreement, Google grants you a royalty-free, non-assignable, non-exclusive, non-sublicensable, limited, revocable license to use the SDK, personally or internally within your company or organization, solely to develop and distribute applications to run on the Android platform.
26 |
27 | 3.2 You agree that Google or third parties own all legal right, title and interest in and to the SDK, including any Intellectual Property Rights that subsist in the SDK. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you.
28 |
29 | 3.3 You may not use the SDK for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the SDK or any part of the SDK; or (b) load any part of the SDK onto a mobile handset or any other hardware device except a personal computer, combine any part of the SDK with other software, or distribute any software or device incorporating a part of the SDK.
30 |
31 | 3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the SDK.
32 |
33 | 3.5 Use, reproduction and distribution of components of the SDK licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. You agree to remain a licensee in good standing in regard to such open source software licenses under all the rights granted and to refrain from any actions that may terminate, suspend, or breach such rights.
34 |
35 | 3.6 You agree that the form and nature of the SDK that Google provides may change without prior notice to you and that future versions of the SDK may be incompatible with applications developed on previous versions of the SDK. You agree that Google may stop (permanently or temporarily) providing the SDK (or any features within the SDK) to you or to users generally at Google's sole discretion, without prior notice to you.
36 |
37 | 3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features.
38 |
39 | 3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the SDK.
40 |
41 | 4. Use of the SDK by You
42 |
43 | 4.1 Google agrees that nothing in the License Agreement gives Google any right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the SDK, including any intellectual property rights that subsist in those applications.
44 |
45 | 4.2 You agree to use the SDK and write applications only for purposes that are permitted by (a) the License Agreement, and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
46 |
47 | 4.3 You agree that if you use the SDK to develop applications, you will protect the privacy and legal rights of users. If users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If users provide you with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, each user has given you permission to do so.
48 |
49 | 4.4 You agree that you will not engage in any activity with the SDK, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of Google or any third party.
50 |
51 | 4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so.
52 |
53 | 4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach.
54 |
55 | 5. Your Developer Credentials
56 |
57 | 5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials.
58 |
59 | 6. Privacy and Information
60 |
61 | 6.1 In order to continually innovate and improve the SDK, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the SDK are being used and how they are being used. Before any of this information is collected, the SDK will notify you and seek your consent. If you withhold consent, the information will not be collected.
62 |
63 | 6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in accordance with Google's Privacy Policy located at http://www.google.com/policies/privacy/.
64 |
65 | 7. Third Party Applications
66 |
67 | 7.1 If you use the SDK to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources.
68 |
69 | 7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners.
70 |
71 | 7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party.
72 |
73 | 8. Using Google APIs
74 |
75 | 8.1 Google APIs
76 |
77 | 8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service.
78 |
79 | 8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so.
80 |
81 | 9. Terminating the License Agreement
82 |
83 | 9.1 The License Agreement will continue to apply until terminated by either you or Google as set out below.
84 |
85 | 9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the SDK and any relevant developer credentials.
86 |
87 | 9.3 Google may at any time, terminate the License Agreement, with or without cause, upon notice to you.
88 |
89 | 9.4 The License Agreement will automatically terminate without notice or other action when Google ceases to provide the SDK or certain parts of the SDK to users in the country in which you are resident or from which you use the service.
90 |
91 | 9.5 When the License Agreement is terminated, the license granted to you in the License Agreement will terminate, you will immediately cease all use of the SDK, and the provisions of paragraphs 10, 11, 12 and 14 shall survive indefinitely.
92 |
93 | 10. DISCLAIMERS
94 |
95 | 10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
96 |
97 | 10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. WITHOUT LIMITING THE FOREGOING, YOU UNDERSTAND THAT THE SDK MAY CONTAIN ERRORS, DEFECTS AND SECURITY VULNERABILITIES THAT CAN RESULT IN SIGNIFICANT DAMAGE, INCLUDING THE COMPLETE, IRRECOVERABLE LOSS OF USE OF YOUR COMPUTER SYSTEM OR OTHER DEVICE.
98 |
99 | 10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
100 |
101 | 11. LIMITATION OF LIABILITY
102 |
103 | 11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
104 |
105 | 12. Indemnification
106 |
107 | 12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the SDK, (b) any application you develop on the SDK that infringes any Intellectual Property Rights of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you of the License Agreement.
108 |
109 | 13. Changes to the License Agreement
110 |
111 | 13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the SDK is made available.
112 |
113 | 14. General Legal Terms
114 |
115 | 14.1 The License Agreement constitutes the whole legal agreement between you and Google and governs your use of the SDK (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the SDK.
116 |
117 | 14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
118 |
119 | 14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable.
120 |
121 | 14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement.
122 |
123 | 14.5 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
124 |
125 | 14.6 The License Agreement may not be assigned or transferred by you without the prior written approval of Google, and any attempted assignment without such approval will be void. You shall not delegate your responsibilities or obligations under the License Agreement without the prior written approval of Google.
126 |
127 | 14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
128 |
129 | June 2014.
130 | googleGoogle Inc.300Google Play APK Expansion Library, rev 3
--------------------------------------------------------------------------------
/zip_file/src/com/google/android/vending/expansion/zipfile/ZipResourceFile.java:
--------------------------------------------------------------------------------
1 |
2 | package com.google.android.vending.expansion.zipfile;
3 |
4 | /*
5 | * Copyright (C) 2012 The Android Open Source Project
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 | import android.content.res.AssetFileDescriptor;
20 | import android.os.ParcelFileDescriptor;
21 | import android.util.Log;
22 |
23 | import java.io.EOFException;
24 | import java.io.File;
25 | import java.io.FileNotFoundException;
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.io.RandomAccessFile;
29 | import java.nio.ByteBuffer;
30 | import java.nio.ByteOrder;
31 | import java.nio.MappedByteBuffer;
32 | import java.nio.channels.FileChannel;
33 | import java.util.Collection;
34 | import java.util.HashMap;
35 | import java.util.Vector;
36 | import java.util.zip.ZipEntry;
37 | import java.util.zip.ZipFile;
38 |
39 | public class ZipResourceFile {
40 |
41 | //
42 | // Read-only access to Zip archives, with minimal heap allocation.
43 | //
44 | static final String LOG_TAG = "zipro";
45 | static final boolean LOGV = false;
46 |
47 | // 4-byte number
48 | static private int swapEndian(int i)
49 | {
50 | return ((i & 0xff) << 24) + ((i & 0xff00) << 8) + ((i & 0xff0000) >>> 8)
51 | + ((i >>> 24) & 0xff);
52 | }
53 |
54 | // 2-byte number
55 | static private int swapEndian(short i)
56 | {
57 | return ((i & 0x00FF) << 8 | (i & 0xFF00) >>> 8);
58 | }
59 |
60 | /*
61 | * Zip file constants.
62 | */
63 | static final int kEOCDSignature = 0x06054b50;
64 | static final int kEOCDLen = 22;
65 | static final int kEOCDNumEntries = 8; // offset to #of entries in file
66 | static final int kEOCDSize = 12; // size of the central directory
67 | static final int kEOCDFileOffset = 16; // offset to central directory
68 |
69 | static final int kMaxCommentLen = 65535; // longest possible in ushort
70 | static final int kMaxEOCDSearch = (kMaxCommentLen + kEOCDLen);
71 |
72 | static final int kLFHSignature = 0x04034b50;
73 | static final int kLFHLen = 30; // excluding variable-len fields
74 | static final int kLFHNameLen = 26; // offset to filename length
75 | static final int kLFHExtraLen = 28; // offset to extra length
76 |
77 | static final int kCDESignature = 0x02014b50;
78 | static final int kCDELen = 46; // excluding variable-len fields
79 | static final int kCDEMethod = 10; // offset to compression method
80 | static final int kCDEModWhen = 12; // offset to modification timestamp
81 | static final int kCDECRC = 16; // offset to entry CRC
82 | static final int kCDECompLen = 20; // offset to compressed length
83 | static final int kCDEUncompLen = 24; // offset to uncompressed length
84 | static final int kCDENameLen = 28; // offset to filename length
85 | static final int kCDEExtraLen = 30; // offset to extra length
86 | static final int kCDECommentLen = 32; // offset to comment length
87 | static final int kCDELocalOffset = 42; // offset to local hdr
88 |
89 | static final int kCompressStored = 0; // no compression
90 | static final int kCompressDeflated = 8; // standard deflate
91 |
92 | /*
93 | * The values we return for ZipEntryRO use 0 as an invalid value, so we want
94 | * to adjust the hash table index by a fixed amount. Using a large value
95 | * helps insure that people don't mix & match arguments, e.g. to
96 | * findEntryByIndex().
97 | */
98 | static final int kZipEntryAdj = 10000;
99 |
100 | static public final class ZipEntryRO {
101 | public ZipEntryRO(final String zipFileName, final File file, final String fileName) {
102 | mFileName = fileName;
103 | mZipFileName = zipFileName;
104 | mFile = file;
105 | }
106 |
107 | public final File mFile;
108 | public final String mFileName;
109 | public final String mZipFileName;
110 | public long mLocalHdrOffset; // offset of local file header
111 |
112 | /* useful stuff from the directory entry */
113 | public int mMethod;
114 | public long mWhenModified;
115 | public long mCRC32;
116 | public long mCompressedLength;
117 | public long mUncompressedLength;
118 |
119 | public long mOffset = -1;
120 |
121 | public void setOffsetFromFile(RandomAccessFile f, ByteBuffer buf) throws IOException {
122 | long localHdrOffset = mLocalHdrOffset;
123 | try {
124 | f.seek(localHdrOffset);
125 | f.readFully(buf.array());
126 | if (buf.getInt(0) != kLFHSignature) {
127 | Log.w(LOG_TAG, "didn't find signature at start of lfh");
128 | throw new IOException();
129 | }
130 | int nameLen = buf.getShort(kLFHNameLen) & 0xFFFF;
131 | int extraLen = buf.getShort(kLFHExtraLen) & 0xFFFF;
132 | mOffset = localHdrOffset + kLFHLen + nameLen + extraLen;
133 | } catch (FileNotFoundException e) {
134 | e.printStackTrace();
135 | } catch (IOException ioe) {
136 | ioe.printStackTrace();
137 | }
138 | }
139 |
140 | /**
141 | * Calculates the offset of the start of the Zip file entry within the
142 | * Zip file.
143 | *
144 | * @return the offset, in bytes from the start of the file of the entry
145 | */
146 | public long getOffset() {
147 | return mOffset;
148 | }
149 |
150 | /**
151 | * isUncompressed
152 | *
153 | * @return true if the file is stored in uncompressed form
154 | */
155 | public boolean isUncompressed() {
156 | return mMethod == kCompressStored;
157 | }
158 |
159 | public AssetFileDescriptor getAssetFileDescriptor() {
160 | if (mMethod == kCompressStored) {
161 | ParcelFileDescriptor pfd;
162 | try {
163 | pfd = ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY);
164 | return new AssetFileDescriptor(pfd, getOffset(), mUncompressedLength);
165 | } catch (FileNotFoundException e) {
166 | // TODO Auto-generated catch block
167 | e.printStackTrace();
168 | }
169 | }
170 | return null;
171 | }
172 |
173 | public String getZipFileName() {
174 | return mZipFileName;
175 | }
176 |
177 | public File getZipFile() {
178 | return mFile;
179 | }
180 |
181 | }
182 |
183 | private HashMap mHashMap = new HashMap();
184 |
185 | /* for reading compressed files */
186 | public HashMap mZipFiles = new HashMap();
187 |
188 | public ZipResourceFile(String zipFileName) throws IOException {
189 | addPatchFile(zipFileName);
190 | }
191 |
192 | ZipEntryRO[] getEntriesAt(String path) {
193 | Vector zev = new Vector();
194 | Collection values = mHashMap.values();
195 | if (null == path)
196 | path = "";
197 | int length = path.length();
198 | for (ZipEntryRO ze : values) {
199 | if (ze.mFileName.startsWith(path)) {
200 | if (-1 == ze.mFileName.indexOf('/', length)) {
201 | zev.add(ze);
202 | }
203 | }
204 | }
205 | ZipEntryRO[] entries = new ZipEntryRO[zev.size()];
206 | return zev.toArray(entries);
207 | }
208 |
209 | public ZipEntryRO[] getAllEntries() {
210 | Collection values = mHashMap.values();
211 | return values.toArray(new ZipEntryRO[values.size()]);
212 | }
213 |
214 | /**
215 | * getAssetFileDescriptor allows for ZipResourceFile to directly feed
216 | * Android API's that want an fd, offset, and length such as the
217 | * MediaPlayer. It also allows for the class to be used in a content
218 | * provider that can feed video players. The file must be stored
219 | * (non-compressed) in the Zip file for this to work.
220 | *
221 | * @param assetPath
222 | * @return the asset file descriptor for the file, or null if the file isn't
223 | * present or is stored compressed
224 | */
225 | public AssetFileDescriptor getAssetFileDescriptor(String assetPath) {
226 | ZipEntryRO entry = mHashMap.get(assetPath);
227 | if (null != entry) {
228 | return entry.getAssetFileDescriptor();
229 | }
230 | return null;
231 | }
232 |
233 | /**
234 | * getInputStream returns an AssetFileDescriptor.AutoCloseInputStream
235 | * associated with the asset that is contained in the Zip file, or a
236 | * standard ZipInputStream if necessary to uncompress the file
237 | *
238 | * @param assetPath
239 | * @return an input stream for the named asset path, or null if not found
240 | * @throws IOException
241 | */
242 | public InputStream getInputStream(String assetPath) throws IOException {
243 | ZipEntryRO entry = mHashMap.get(assetPath);
244 | if (null != entry) {
245 | if (entry.isUncompressed()) {
246 | return entry.getAssetFileDescriptor().createInputStream();
247 | } else {
248 | ZipFile zf = mZipFiles.get(entry.getZipFile());
249 | /** read compressed files **/
250 | if (null == zf) {
251 | zf = new ZipFile(entry.getZipFile(), ZipFile.OPEN_READ);
252 | mZipFiles.put(entry.getZipFile(), zf);
253 | }
254 | ZipEntry zi = zf.getEntry(assetPath);
255 | if (null != zi)
256 | return zf.getInputStream(zi);
257 | }
258 | }
259 | return null;
260 | }
261 |
262 | ByteBuffer mLEByteBuffer = ByteBuffer.allocate(4);
263 |
264 | static private int read4LE(RandomAccessFile f) throws EOFException, IOException {
265 | return swapEndian(f.readInt());
266 | }
267 |
268 | /*
269 | * Opens the specified file read-only. We memory-map the entire thing and
270 | * close the file before returning.
271 | */
272 | void addPatchFile(String zipFileName) throws IOException
273 | {
274 | File file = new File(zipFileName);
275 | RandomAccessFile f = new RandomAccessFile(file, "r");
276 | long fileLength = f.length();
277 |
278 | if (fileLength < kEOCDLen) {
279 | throw new java.io.IOException();
280 | }
281 |
282 | long readAmount = kMaxEOCDSearch;
283 | if (readAmount > fileLength)
284 | readAmount = fileLength;
285 |
286 | /*
287 | * Make sure this is a Zip archive.
288 | */
289 | f.seek(0);
290 |
291 | int header = read4LE(f);
292 | if (header == kEOCDSignature) {
293 | Log.i(LOG_TAG, "Found Zip archive, but it looks empty");
294 | throw new IOException();
295 | } else if (header != kLFHSignature) {
296 | Log.v(LOG_TAG, "Not a Zip archive");
297 | throw new IOException();
298 | }
299 |
300 | /*
301 | * Perform the traditional EOCD snipe hunt. We're searching for the End
302 | * of Central Directory magic number, which appears at the start of the
303 | * EOCD block. It's followed by 18 bytes of EOCD stuff and up to 64KB of
304 | * archive comment. We need to read the last part of the file into a
305 | * buffer, dig through it to find the magic number, parse some values
306 | * out, and use those to determine the extent of the CD. We start by
307 | * pulling in the last part of the file.
308 | */
309 | long searchStart = fileLength - readAmount;
310 |
311 | f.seek(searchStart);
312 | ByteBuffer bbuf = ByteBuffer.allocate((int) readAmount);
313 | byte[] buffer = bbuf.array();
314 | f.readFully(buffer);
315 | bbuf.order(ByteOrder.LITTLE_ENDIAN);
316 |
317 | /*
318 | * Scan backward for the EOCD magic. In an archive without a trailing
319 | * comment, we'll find it on the first try. (We may want to consider
320 | * doing an initial minimal read; if we don't find it, retry with a
321 | * second read as above.)
322 | */
323 |
324 | // EOCD == 0x50, 0x4b, 0x05, 0x06
325 | int eocdIdx;
326 | for (eocdIdx = buffer.length - kEOCDLen; eocdIdx >= 0; eocdIdx--) {
327 | if (buffer[eocdIdx] == 0x50 && bbuf.getInt(eocdIdx) == kEOCDSignature)
328 | {
329 | if (LOGV) {
330 | Log.v(LOG_TAG, "+++ Found EOCD at index: " + eocdIdx);
331 | }
332 | break;
333 | }
334 | }
335 |
336 | if (eocdIdx < 0) {
337 | Log.d(LOG_TAG, "Zip: EOCD not found, " + zipFileName + " is not zip");
338 | }
339 |
340 | /*
341 | * Grab the CD offset and size, and the number of entries in the
342 | * archive. After that, we can release our EOCD hunt buffer.
343 | */
344 |
345 | int numEntries = bbuf.getShort(eocdIdx + kEOCDNumEntries);
346 | long dirSize = bbuf.getInt(eocdIdx + kEOCDSize) & 0xffffffffL;
347 | long dirOffset = bbuf.getInt(eocdIdx + kEOCDFileOffset) & 0xffffffffL;
348 |
349 | // Verify that they look reasonable.
350 | if (dirOffset + dirSize > fileLength) {
351 | Log.w(LOG_TAG, "bad offsets (dir " + dirOffset + ", size " + dirSize + ", eocd "
352 | + eocdIdx + ")");
353 | throw new IOException();
354 | }
355 | if (numEntries == 0) {
356 | Log.w(LOG_TAG, "empty archive?");
357 | throw new IOException();
358 | }
359 |
360 | if (LOGV) {
361 | Log.v(LOG_TAG, "+++ numEntries=" + numEntries + " dirSize=" + dirSize + " dirOffset="
362 | + dirOffset);
363 | }
364 |
365 | MappedByteBuffer directoryMap = f.getChannel()
366 | .map(FileChannel.MapMode.READ_ONLY, dirOffset, dirSize);
367 | directoryMap.order(ByteOrder.LITTLE_ENDIAN);
368 |
369 | byte[] tempBuf = new byte[0xffff];
370 |
371 | /*
372 | * Walk through the central directory, adding entries to the hash table.
373 | */
374 |
375 | int currentOffset = 0;
376 |
377 | /*
378 | * Allocate the local directory information
379 | */
380 | ByteBuffer buf = ByteBuffer.allocate(kLFHLen);
381 | buf.order(ByteOrder.LITTLE_ENDIAN);
382 |
383 | for (int i = 0; i < numEntries; i++) {
384 | if (directoryMap.getInt(currentOffset) != kCDESignature) {
385 | Log.w(LOG_TAG, "Missed a central dir sig (at " + currentOffset + ")");
386 | throw new IOException();
387 | }
388 |
389 | /* useful stuff from the directory entry */
390 | int fileNameLen = directoryMap.getShort(currentOffset + kCDENameLen) & 0xffff;
391 | int extraLen = directoryMap.getShort(currentOffset + kCDEExtraLen) & 0xffff;
392 | int commentLen = directoryMap.getShort(currentOffset + kCDECommentLen) & 0xffff;
393 |
394 | /* get the CDE filename */
395 |
396 | directoryMap.position(currentOffset + kCDELen);
397 | directoryMap.get(tempBuf, 0, fileNameLen);
398 | directoryMap.position(0);
399 |
400 | /* UTF-8 on Android */
401 | String str = new String(tempBuf, 0, fileNameLen);
402 | if (LOGV) {
403 | Log.v(LOG_TAG, "Filename: " + str);
404 | }
405 |
406 | ZipEntryRO ze = new ZipEntryRO(zipFileName, file, str);
407 | ze.mMethod = directoryMap.getShort(currentOffset + kCDEMethod) & 0xffff;
408 | ze.mWhenModified = directoryMap.getInt(currentOffset + kCDEModWhen) & 0xffffffffL;
409 | ze.mCRC32 = directoryMap.getLong(currentOffset + kCDECRC) & 0xffffffffL;
410 | ze.mCompressedLength = directoryMap.getLong(currentOffset + kCDECompLen) & 0xffffffffL;
411 | ze.mUncompressedLength = directoryMap.getLong(currentOffset + kCDEUncompLen) & 0xffffffffL;
412 | ze.mLocalHdrOffset = directoryMap.getInt(currentOffset + kCDELocalOffset) & 0xffffffffL;
413 |
414 | // set the offsets
415 | buf.clear();
416 | ze.setOffsetFromFile(f, buf);
417 |
418 | // put file into hash
419 | mHashMap.put(str, ze);
420 |
421 | // go to next directory entry
422 | currentOffset += kCDELen + fileNameLen + extraLen + commentLen;
423 | }
424 | if (LOGV) {
425 | Log.v(LOG_TAG, "+++ zip good scan " + numEntries + " entries");
426 | }
427 | }
428 | }
429 |
--------------------------------------------------------------------------------
/apkx_library/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.expansion.downloader.impl;
18 |
19 | import android.content.ContentValues;
20 | import android.content.Context;
21 | import android.database.Cursor;
22 | import android.database.sqlite.SQLiteDatabase;
23 | import android.database.sqlite.SQLiteDoneException;
24 | import android.database.sqlite.SQLiteOpenHelper;
25 | import android.database.sqlite.SQLiteStatement;
26 | import android.provider.BaseColumns;
27 | import android.util.Log;
28 |
29 | public class DownloadsDB {
30 | private static final String DATABASE_NAME = "DownloadsDB";
31 | private static final int DATABASE_VERSION = 7;
32 | public static final String LOG_TAG = DownloadsDB.class.getName();
33 | final SQLiteOpenHelper mHelper;
34 | SQLiteStatement mGetDownloadByIndex;
35 | SQLiteStatement mUpdateCurrentBytes;
36 | private static DownloadsDB mDownloadsDB;
37 | long mMetadataRowID = -1;
38 | int mVersionCode = -1;
39 | int mStatus = -1;
40 | int mFlags;
41 |
42 | static public synchronized DownloadsDB getDB(Context paramContext) {
43 | if (null == mDownloadsDB) {
44 | return new DownloadsDB(paramContext);
45 | }
46 | return mDownloadsDB;
47 | }
48 |
49 | private SQLiteStatement getDownloadByIndexStatement() {
50 | if (null == mGetDownloadByIndex) {
51 | mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement(
52 | "SELECT " + BaseColumns._ID + " FROM "
53 | + DownloadColumns.TABLE_NAME + " WHERE "
54 | + DownloadColumns.INDEX + " = ?");
55 | }
56 | return mGetDownloadByIndex;
57 | }
58 |
59 | private SQLiteStatement getUpdateCurrentBytesStatement() {
60 | if (null == mUpdateCurrentBytes) {
61 | mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement(
62 | "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES
63 | + " = ?" +
64 | " WHERE " + DownloadColumns.INDEX + " = ?");
65 | }
66 | return mUpdateCurrentBytes;
67 | }
68 |
69 | private DownloadsDB(Context paramContext) {
70 | this.mHelper = new DownloadsContentDBHelper(paramContext);
71 | final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
72 | // Query for the version code, the row ID of the metadata (for future
73 | // updating) the status and the flags
74 | Cursor cur = sqldb.rawQuery("SELECT " +
75 | MetadataColumns.APKVERSION + "," +
76 | BaseColumns._ID + "," +
77 | MetadataColumns.DOWNLOAD_STATUS + "," +
78 | MetadataColumns.FLAGS +
79 | " FROM "
80 | + MetadataColumns.TABLE_NAME + " LIMIT 1", null);
81 | if (null != cur && cur.moveToFirst()) {
82 | mVersionCode = cur.getInt(0);
83 | mMetadataRowID = cur.getLong(1);
84 | mStatus = cur.getInt(2);
85 | mFlags = cur.getInt(3);
86 | cur.close();
87 | }
88 | mDownloadsDB = this;
89 | }
90 |
91 | protected DownloadInfo getDownloadInfoByFileName(String fileName) {
92 | final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
93 | Cursor itemcur = null;
94 | try {
95 | itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION,
96 | DownloadColumns.FILENAME + " = ?",
97 | new String[] {
98 | fileName
99 | }, null, null, null);
100 | if (null != itemcur && itemcur.moveToFirst()) {
101 | return getDownloadInfoFromCursor(itemcur);
102 | }
103 | } finally {
104 | if (null != itemcur)
105 | itemcur.close();
106 | }
107 | return null;
108 | }
109 |
110 | public long getIDForDownloadInfo(final DownloadInfo di) {
111 | return getIDByIndex(di.mIndex);
112 | }
113 |
114 | public long getIDByIndex(int index) {
115 | SQLiteStatement downloadByIndex = getDownloadByIndexStatement();
116 | downloadByIndex.clearBindings();
117 | downloadByIndex.bindLong(1, index);
118 | try {
119 | return downloadByIndex.simpleQueryForLong();
120 | } catch (SQLiteDoneException e) {
121 | return -1;
122 | }
123 | }
124 |
125 | public void updateDownloadCurrentBytes(final DownloadInfo di) {
126 | SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement();
127 | downloadCurrentBytes.clearBindings();
128 | downloadCurrentBytes.bindLong(1, di.mCurrentBytes);
129 | downloadCurrentBytes.bindLong(2, di.mIndex);
130 | downloadCurrentBytes.execute();
131 | }
132 |
133 | public void close() {
134 | this.mHelper.close();
135 | }
136 |
137 | protected static class DownloadsContentDBHelper extends SQLiteOpenHelper {
138 | DownloadsContentDBHelper(Context paramContext) {
139 | super(paramContext, DATABASE_NAME, null, DATABASE_VERSION);
140 | }
141 |
142 | private String createTableQueryFromArray(String paramString,
143 | String[][] paramArrayOfString) {
144 | StringBuilder localStringBuilder = new StringBuilder();
145 | localStringBuilder.append("CREATE TABLE ");
146 | localStringBuilder.append(paramString);
147 | localStringBuilder.append(" (");
148 | int i = paramArrayOfString.length;
149 | for (int j = 0;; j++) {
150 | if (j >= i) {
151 | localStringBuilder
152 | .setLength(localStringBuilder.length() - 1);
153 | localStringBuilder.append(");");
154 | return localStringBuilder.toString();
155 | }
156 | String[] arrayOfString = paramArrayOfString[j];
157 | localStringBuilder.append(' ');
158 | localStringBuilder.append(arrayOfString[0]);
159 | localStringBuilder.append(' ');
160 | localStringBuilder.append(arrayOfString[1]);
161 | localStringBuilder.append(',');
162 | }
163 | }
164 |
165 | /**
166 | * These two arrays must match and have the same order. For every Schema
167 | * there must be a corresponding table name.
168 | */
169 | static final private String[][][] sSchemas = {
170 | DownloadColumns.SCHEMA, MetadataColumns.SCHEMA
171 | };
172 |
173 | static final private String[] sTables = {
174 | DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME
175 | };
176 |
177 | /**
178 | * Goes through all of the tables in sTables and drops each table if it
179 | * exists. Altered to no longer make use of reflection.
180 | */
181 | private void dropTables(SQLiteDatabase paramSQLiteDatabase) {
182 | for (String table : sTables) {
183 | try {
184 | paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table);
185 | } catch (Exception localException) {
186 | localException.printStackTrace();
187 | }
188 | }
189 | }
190 |
191 | /**
192 | * Goes through all of the tables in sTables and creates a database with
193 | * the corresponding schema described in sSchemas. Altered to no longer
194 | * make use of reflection.
195 | */
196 | public void onCreate(SQLiteDatabase paramSQLiteDatabase) {
197 | int numSchemas = sSchemas.length;
198 | for (int i = 0; i < numSchemas; i++) {
199 | try {
200 | String[][] schema = (String[][]) sSchemas[i];
201 | paramSQLiteDatabase.execSQL(createTableQueryFromArray(
202 | sTables[i], schema));
203 | } catch (Exception localException) {
204 | while (true)
205 | localException.printStackTrace();
206 | }
207 | }
208 | }
209 |
210 | public void onUpgrade(SQLiteDatabase paramSQLiteDatabase,
211 | int paramInt1, int paramInt2) {
212 | Log.w(DownloadsContentDBHelper.class.getName(),
213 | "Upgrading database from version " + paramInt1 + " to "
214 | + paramInt2 + ", which will destroy all old data");
215 | dropTables(paramSQLiteDatabase);
216 | onCreate(paramSQLiteDatabase);
217 | }
218 | }
219 |
220 | public static class MetadataColumns implements BaseColumns {
221 | public static final String APKVERSION = "APKVERSION";
222 | public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS";
223 | public static final String FLAGS = "DOWNLOADFLAGS";
224 |
225 | public static final String[][] SCHEMA = {
226 | {
227 | BaseColumns._ID, "INTEGER PRIMARY KEY"
228 | },
229 | {
230 | APKVERSION, "INTEGER"
231 | }, {
232 | DOWNLOAD_STATUS, "INTEGER"
233 | },
234 | {
235 | FLAGS, "INTEGER"
236 | }
237 | };
238 | public static final String TABLE_NAME = "MetadataColumns";
239 | public static final String _ID = "MetadataColumns._id";
240 | }
241 |
242 | public static class DownloadColumns implements BaseColumns {
243 | public static final String INDEX = "FILEIDX";
244 | public static final String URI = "URI";
245 | public static final String FILENAME = "FN";
246 | public static final String ETAG = "ETAG";
247 |
248 | public static final String TOTALBYTES = "TOTALBYTES";
249 | public static final String CURRENTBYTES = "CURRENTBYTES";
250 | public static final String LASTMOD = "LASTMOD";
251 |
252 | public static final String STATUS = "STATUS";
253 | public static final String CONTROL = "CONTROL";
254 | public static final String NUM_FAILED = "FAILCOUNT";
255 | public static final String RETRY_AFTER = "RETRYAFTER";
256 | public static final String REDIRECT_COUNT = "REDIRECTCOUNT";
257 |
258 | public static final String[][] SCHEMA = {
259 | {
260 | BaseColumns._ID, "INTEGER PRIMARY KEY"
261 | },
262 | {
263 | INDEX, "INTEGER UNIQUE"
264 | }, {
265 | URI, "TEXT"
266 | },
267 | {
268 | FILENAME, "TEXT UNIQUE"
269 | }, {
270 | ETAG, "TEXT"
271 | },
272 | {
273 | TOTALBYTES, "INTEGER"
274 | }, {
275 | CURRENTBYTES, "INTEGER"
276 | },
277 | {
278 | LASTMOD, "INTEGER"
279 | }, {
280 | STATUS, "INTEGER"
281 | },
282 | {
283 | CONTROL, "INTEGER"
284 | }, {
285 | NUM_FAILED, "INTEGER"
286 | },
287 | {
288 | RETRY_AFTER, "INTEGER"
289 | }, {
290 | REDIRECT_COUNT, "INTEGER"
291 | }
292 | };
293 | public static final String TABLE_NAME = "DownloadColumns";
294 | public static final String _ID = "DownloadColumns._id";
295 | }
296 |
297 | private static final String[] DC_PROJECTION = {
298 | DownloadColumns.FILENAME,
299 | DownloadColumns.URI, DownloadColumns.ETAG,
300 | DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES,
301 | DownloadColumns.LASTMOD, DownloadColumns.STATUS,
302 | DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED,
303 | DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT,
304 | DownloadColumns.INDEX
305 | };
306 |
307 | private static final int FILENAME_IDX = 0;
308 | private static final int URI_IDX = 1;
309 | private static final int ETAG_IDX = 2;
310 | private static final int TOTALBYTES_IDX = 3;
311 | private static final int CURRENTBYTES_IDX = 4;
312 | private static final int LASTMOD_IDX = 5;
313 | private static final int STATUS_IDX = 6;
314 | private static final int CONTROL_IDX = 7;
315 | private static final int NUM_FAILED_IDX = 8;
316 | private static final int RETRY_AFTER_IDX = 9;
317 | private static final int REDIRECT_COUNT_IDX = 10;
318 | private static final int INDEX_IDX = 11;
319 |
320 | /**
321 | * This function will add a new file to the database if it does not exist.
322 | *
323 | * @param di DownloadInfo that we wish to store
324 | * @return the row id of the record to be updated/inserted, or -1
325 | */
326 | public boolean updateDownload(DownloadInfo di) {
327 | ContentValues cv = new ContentValues();
328 | cv.put(DownloadColumns.INDEX, di.mIndex);
329 | cv.put(DownloadColumns.FILENAME, di.mFileName);
330 | cv.put(DownloadColumns.URI, di.mUri);
331 | cv.put(DownloadColumns.ETAG, di.mETag);
332 | cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes);
333 | cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes);
334 | cv.put(DownloadColumns.LASTMOD, di.mLastMod);
335 | cv.put(DownloadColumns.STATUS, di.mStatus);
336 | cv.put(DownloadColumns.CONTROL, di.mControl);
337 | cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed);
338 | cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter);
339 | cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount);
340 | return updateDownload(di, cv);
341 | }
342 |
343 | public boolean updateDownload(DownloadInfo di, ContentValues cv) {
344 | long id = di == null ? -1 : getIDForDownloadInfo(di);
345 | try {
346 | final SQLiteDatabase sqldb = mHelper.getWritableDatabase();
347 | if (id != -1) {
348 | if (1 != sqldb.update(DownloadColumns.TABLE_NAME,
349 | cv, DownloadColumns._ID + " = " + id, null)) {
350 | return false;
351 | }
352 | } else {
353 | return -1 != sqldb.insert(DownloadColumns.TABLE_NAME,
354 | DownloadColumns.URI, cv);
355 | }
356 | } catch (android.database.sqlite.SQLiteException ex) {
357 | ex.printStackTrace();
358 | }
359 | return false;
360 | }
361 |
362 | public int getLastCheckedVersionCode() {
363 | return mVersionCode;
364 | }
365 |
366 | public boolean isDownloadRequired() {
367 | final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
368 | Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM "
369 | + DownloadColumns.TABLE_NAME + " WHERE "
370 | + DownloadColumns.STATUS + " <> 0", null);
371 | try {
372 | if (null != cur && cur.moveToFirst()) {
373 | return 0 == cur.getInt(0);
374 | }
375 | } finally {
376 | if (null != cur)
377 | cur.close();
378 | }
379 | return true;
380 | }
381 |
382 | public int getFlags() {
383 | return mFlags;
384 | }
385 |
386 | public boolean updateFlags(int flags) {
387 | if (mFlags != flags) {
388 | ContentValues cv = new ContentValues();
389 | cv.put(MetadataColumns.FLAGS, flags);
390 | if (updateMetadata(cv)) {
391 | mFlags = flags;
392 | return true;
393 | } else {
394 | return false;
395 | }
396 | } else {
397 | return true;
398 | }
399 | };
400 |
401 | public boolean updateStatus(int status) {
402 | if (mStatus != status) {
403 | ContentValues cv = new ContentValues();
404 | cv.put(MetadataColumns.DOWNLOAD_STATUS, status);
405 | if (updateMetadata(cv)) {
406 | mStatus = status;
407 | return true;
408 | } else {
409 | return false;
410 | }
411 | } else {
412 | return true;
413 | }
414 | };
415 |
416 | public boolean updateMetadata(ContentValues cv) {
417 | final SQLiteDatabase sqldb = mHelper.getWritableDatabase();
418 | if (-1 == this.mMetadataRowID) {
419 | long newID = sqldb.insert(MetadataColumns.TABLE_NAME,
420 | MetadataColumns.APKVERSION, cv);
421 | if (-1 == newID)
422 | return false;
423 | mMetadataRowID = newID;
424 | } else {
425 | if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv,
426 | BaseColumns._ID + " = " + mMetadataRowID, null))
427 | return false;
428 | }
429 | return true;
430 | }
431 |
432 | public boolean updateMetadata(int apkVersion, int downloadStatus) {
433 | ContentValues cv = new ContentValues();
434 | cv.put(MetadataColumns.APKVERSION, apkVersion);
435 | cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus);
436 | if (updateMetadata(cv)) {
437 | mVersionCode = apkVersion;
438 | mStatus = downloadStatus;
439 | return true;
440 | } else {
441 | return false;
442 | }
443 | };
444 |
445 | public boolean updateFromDb(DownloadInfo di) {
446 | final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
447 | Cursor cur = null;
448 | try {
449 | cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION,
450 | DownloadColumns.FILENAME + "= ?",
451 | new String[] {
452 | di.mFileName
453 | }, null, null, null);
454 | if (null != cur && cur.moveToFirst()) {
455 | setDownloadInfoFromCursor(di, cur);
456 | return true;
457 | }
458 | return false;
459 | } finally {
460 | if (null != cur) {
461 | cur.close();
462 | }
463 | }
464 | }
465 |
466 | public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) {
467 | di.mUri = cur.getString(URI_IDX);
468 | di.mETag = cur.getString(ETAG_IDX);
469 | di.mTotalBytes = cur.getLong(TOTALBYTES_IDX);
470 | di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX);
471 | di.mLastMod = cur.getLong(LASTMOD_IDX);
472 | di.mStatus = cur.getInt(STATUS_IDX);
473 | di.mControl = cur.getInt(CONTROL_IDX);
474 | di.mNumFailed = cur.getInt(NUM_FAILED_IDX);
475 | di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX);
476 | di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX);
477 | }
478 |
479 | public DownloadInfo getDownloadInfoFromCursor(Cursor cur) {
480 | DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX),
481 | cur.getString(FILENAME_IDX), this.getClass().getPackage()
482 | .getName());
483 | setDownloadInfoFromCursor(di, cur);
484 | return di;
485 | }
486 |
487 | public DownloadInfo[] getDownloads() {
488 | final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
489 | Cursor cur = null;
490 | try {
491 | cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null,
492 | null, null, null, null);
493 | if (null != cur && cur.moveToFirst()) {
494 | DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()];
495 | int idx = 0;
496 | do {
497 | DownloadInfo di = getDownloadInfoFromCursor(cur);
498 | retInfos[idx++] = di;
499 | } while (cur.moveToNext());
500 | return retInfos;
501 | }
502 | return null;
503 | } finally {
504 | if (null != cur) {
505 | cur.close();
506 | }
507 | }
508 | }
509 |
510 | }
511 |
--------------------------------------------------------------------------------