On Android 6 devices, we've noticed that expansion files now need that we request the WRITE_EXTERNAL_STORAGE permission at runtime, which allows the user to deny, which doesn't allow us to continue with the app startup.
Is there a way to prevent the expansion files to use the WRITE_EXTERNAL_STORAGE or to prevent the user of denying?
If that request must appear when using expansion files, how are we supposed to handle that gracefully?
I also have this problem. To resolve it for API 23 I download .obb file to private app's file directory instead of obb directory which my app hasn't access to. You can review my solution here .
Usage:
repositories {
maven { url 'https://dl.bintray.com/alexeydanilov/apk-expansion' }
}
dependencies {
compile 'com.danikula.expansion:expansion:VERSION#aar'
compile 'com.danikula.expansion:license:VERSION#aar'
compile 'com.danikula.expansion:zip:VERSION#aar'
}
See the latest versions here.
Seems to be a bug on Android 6.0
https://code.google.com/p/android/issues/detail?id=197287
Related
I was doing apk static analysis and want to fetch all the dependencies used by the developer for the app. Is there a way to fetch the list of dependency library name and version from the APK file?
I was going through android documentation and found that while compile time .properties file get generated and which hold the dependency related information which Google play service use while submitting an app.
reference: https://developer.android.com/studio/build/dependencies#dependency-info-play
Dependency information for Play Console When building your app using
AGP 4.0.0 and higher, the plugin includes metadata that describes the
library dependencies that are compiled into your app. When uploading
your app, the Play Console inspects this metadata to provide alerts
for known issues with SDKs and dependencies your app uses, and, in
some cases, provide actionable feedback to resolve those issues.
The data is compressed, encrypted by a Google Play signing key, and
stored in the signing block of your release app. We recommend keeping
this dependencies file for a safe and positive user experience.
However, if you’d rather not share this information, you can opt out
by including the following dependenciesInfo block in your module’s
build.gradle file:
I have a feature implementation which fully relies on Direct File path access to iterate over files and read files.
Note that the feature only access Shared storage files (not App specific files of other apps).
So according to Google, File API and direct file path access are deprecated in Android 10 and brought back in Android 11.
So, I kept the implementation as is, set targetSdkVersion and compileSdkVersion as 30 (Android 11), set requestLegacyExternalStorage as true and uploaded it to Internal App sharing.
When I tested it on Android 10 and 11, its working with no problems.
My questions -
Is scoped storage implementation not necessary for Android 11?
Will my feature break if I publish to production?
If direct file path access works as is, what is the use of MANAGEMANAGE_EXTERNAL_STORAGE permission? (Ability to access app specific files on External storage?)
As I have to target API 30 by November 1st, wanted to confirm this.
UPDATE: More about what the feature does.
Has a directory path stored in Preferences (Can be changed by user) (Path - /storage/emulated/0/SomeFolder/)
On triggering the function (input is some date time epoch). It will iterate over all the files in the directory (that we have in preferences), checks its last modified time and if it closer to input time, returns the file path. (The files in the directory are created by other app on the phone.)
The file path is then used to upload the file to server.
I have a MyLocationService library, which has dependency from huawei_location_service.Inside I have HMSLocationService class which is the only one using huawei_location_service classes and I use relfection to access that class. Meaning is, if we run app on Huawei and if there is dependency from huawei_location_service, I will get location, otherwise will not. And application should run perfectly on non-hauwei devices without dependency from huawei_location_service.
So when I build MyLocationService.aar I removed huawei_location_service dependency from it's pom file. After that I created a new application and added dependency from MyLocationService.aar. When I check dependencies with command gradlew app:dependencies I don't see any dependency from huawei, but when I create an apk and analyze it, in classes.dex there are classes from huawei_location_service.
Question: How it is possible? And is there any other way to achieve what I want?
P.S. I analyzed also MyLocationService.aar, didn't find any huawei dependency. Is there another way to check dependencies of *.aar files instead of pom or analyzing tool of android studio?
So if someone will be mistaken as me, this answer will help.
The repositories and classes I saw in classes.dex were not coming from hms libraries. As I have imports in my custom classes, that imports' texts were the reason I was seeing huawei folder in classes.dex. Also take attention on the size, and you can see that they are kind of 20 bytes.
So I removed the imports, generate my library again, created apk and analyzed it and woala, no huawei folder is visible.
P.S. *.aars doesn't contain any library if you not put transitive=true. And you need to add dependencies required by your lib in your own applicaiton.
P.S.S. If you have locally or globally publishing your library, maven(Gradle uses maven) creates metadata, so called POM file, as a helper to identify all dependencies that the library needs.
When analyzing an APK, I've found a whole lot of .properties files in the archive's root, indicating the version numbers of Google Play services, Firebase and the Google HTTP client, for example:
version=19.2.0
client=firebase-database
firebase-database_client=19.2.0
These could theoretically be excluded from the package alike:
android {
packagingOptions {
exclude "firebase-*.properties"
exclude "play-services-*.properties"
exclude "google-http-client.version"
}
}
However, this issue suggests it would be the "intended behavior". So is it safe to exclude these seemingly useless files from the package - or are they required by Google Play, in order to expose these version numbers to automated package-content scans? Is the purpose of these version number files documented somewhere?
Here's a screenshot of what I'm talking about:
The part of the documentation, which answers the question, may have not existed back then:
Source: Dependency information for Play Console:
When building your app using AGP 4.0.0 and higher, the plugin includes metadata that describes the library dependencies that are compiled into your app. When uploading your app, the Play Console inspects this metadata to provide alerts for known issues with SDKs and dependencies your app uses, and, in some cases, provide actionable feedback to resolve those issues.
The data is compressed, encrypted by a Google Play signing key, and stored in the signing block of your release app. We recommend keeping this dependencies file for a safe and positive user experience. However, if you’d rather not share this information, you can opt out by including the following dependenciesInfo block in your module’s build.gradle file.
android {
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}
As you already know, the .properties file stores key-value parameters can be read inside the library or from the code outside. Just as an example, let's take the play-service-ads-identifier library:
The properties file is copied from aar base dir to apk root directory. This is how the build system works. I investigate inside this library and I didn't find any reference to the properties contained in the file play-services-ads-identifier.properties. I think it's a common situation for all the remaining library that you use. I suppose the only use of those files is to fast detect which library version is used by the apps.
Moreover I found this note from official documentation:
Note: ProGuard directives are included in the Play services client libraries to preserve the required classes. The Android Plugin for Gradle automatically appends ProGuard configuration files in an AAR (Android ARchive) package and appends that package to your ProGuard configuration. During project creation, Android Studio automatically creates the ProGuard configuration files and build.gradle properties for ProGuard use. To use ProGuard with Android Studio, you must enable the ProGuard setting in your build.gradle buildTypes. For more information, see the ProGuard guide.
As you can read, the Android Plugin for Gradle adds whatever it needs to package the apk, proguard configuration included. If the properties are not removed I think it is a choice. I don't know if they allow the system a faster apk inspection. I looked for more documentation about it and I didn't find anything about those properties file. If they are used by Play Service, it is no documented. Honestly, in my project I just leave those file, they do not affect apk size in a significant way. Considering that the size of these files is minimal, I suggest you leave them where they are.
.properties files mainly used in Java related technologies to store the configurable parameters of an application.
They can also be used for storing strings for Internationalization and localization.
It stores data in form of Key-Value Parameter.
In case of Firebase It Stores user Analytics related information read more here enter link description here
I need to upload a .apk but it exceeds the 50 MB limit.
I read about this on documentation and some questions but I'm having difficulty making this work.
Does anyone have some tutorials that explain how to do this (using Android Studio) and if there is some way to use Gradle to do this?
I've done this on a few projects, but I never figured out a simple way. The instructions at
http://developer.android.com/google/play/expansion-files.html
helped me get my head around the basics, but I found parts of the process (like "android update project") didn't work properly with Gradle.
The instructions below will help you set up your project with the required libraries. After that you can go back to the official docs and figure out what to do with all the stuff you just included.
Add these permissions to AndroidManifest.xml
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
Add Play Services dependency to build.gradle
dependencies {
compile 'com.google.android.gms:play-services:4.0.30'
}
Open SDK Manager and install Google Play APK Expansion Library and Google Play Licensing Library.
Copy java source files from these folders into your project's source/main/java folder:
YOUR-ANDROID-SDK-FOLDER\extras\google\play_apk_expansion\zip_file\src
YOUR-ANDROID-SDK-FOLDER\extras\google\play_apk_expansion\downloader_library\src
YOUR-ANDROID-SDK-FOLDER\extras\google\play_licensing\library\src
Open
YOUR-ANDROID-SDK-FOLDER\extras\google\play_apk_expansion\downloader_library\res
Copy drawable-hdpi, drawable-mdpi and layout into your project's source/main/res folder.
For all files in the values folders, merge content from the file into the matching file in your project.
Create a class which extends android.content.BroadcastReceiver
Add something like this to your manifest:
<receiver android:name="mypackage.MyReceiver"/>
Create a class which extends com.google.android.vending.expansion.downloader.impl.DownloaderService
Add something like this to your manifest:
<service android:name="mypackage.MyDownloaderService"/>
Compile the project and look for errors relating to
import com.android.vending.expansion.downloader.R;
Import your own project resources here instead.
Here is some helfull information for people that end up here in this post since there are some things that changed in the way apk expansions work and also if you are using Android Studio to make the libraries work. So you will need the play services library and the downloader library. (and also the zip tools if you want to use a zip as expansion file and read files and movies directly from the zip without unpacking).
With these libraries it's pretty easy to implement the apk expansion download just make sure:
your activity (the one where you want to implement the downloading
of the expansion pack when the downloading has not been done
automatically) implements IDownloaderClient.
you set up the service & receiver and set them up in your manifest.
The BASE64_PUBLIC_KEY in the service class is correct. Upload the
first apk => look in Services and API's in the developer console
under your app => License code for this app.
This code is used to see if the expansion file can be found on the device:
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
Log.i(TAG, "Expansion filename " +fileName);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true;
}
It uses the class XAPKS wich represents an expansion file, be it either a main or patch file, having a certain filesize(bytes) and associated with a apk version (the one it was first added in).
private static class XAPKFile {
public final boolean mIsMain; // true
public final int mFileVersion; //example 4
public final long mFileSize; //example 126515695L
// example => main expansion that was first introduced in apk version 4 and is 126515695 bytes in size
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
Its also quite easy to read movie files and other stuff directly from the expansion file using the zip tools that google has provided (com.android.vending.zipfile).
First get the expansionfile using the methods provided in the library, the paremeters are integers that represent your main expansion apk version (the apk version where the expansion pack you need was first added) and the patch apk version.
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, APKX_MAIN_APK, APKX_PATCH_APK);
Video
For playing video directly from this zipresourcefile:
AssetFileDescriptor a = expansionFile.getAssetFileDescriptor(pathToFileInsideZip);
Now from this assetFileDescriptor you can get a FileDescriptor and use this in your mediaplayer, the correct syntax to get your mediaplayer to play the video also needs the second and third parameter.. Be it the startoffset and length you can get from the AssetFileDescriptor.
player.setDataSource(a.getFileDescriptor(), a.getStartOffset(), a.getLength());
Other
For all the other stuff (like images) you can just get an inputstream of the zipresourcefile:
expansionFile.getInputStream(pathToFileInsideZip);`
ALSO make sure you don't compress the videos in the zip for this to work!
for example not to compress .mp4 files:
zip -n .mp4 -r zipfile.zip . -x ".*" -x "*/.*"
NOTE 1
You can't use draft anymore as the link to get the expansion file won't be active yet. You have to upload a version to Alpha or Beta first with expansion file. (adding an expansion file is only possible from the second apk you upload and up) So make sure you see the apk expansion file listed when you click the details in the developer publish section under APK.
NOTE 2
If you are using android studio and want to make use of the downloader library don't just copy the package name and java files into your own app src directory. Import the downloader library in eclipse and choose export => gradle build files. Afterwards you can import the library as a module in android studio.
NOTE 3
Not sure of this but I also think it's neccesary to download the app atleast once through the play store and have access to it with the account on your test device. So if you are working with alpha create a google+ test group and add yourself or other test devices to it.
I have just succeeded in making an app that uses the expansion files with Android Studio. Doing this is a monumental challenge. My first advice is that if you just want a big app, make it for an Apple product. Apple accommodates large apps very nicely. On the other hand, Android has made it nearly impossible to make an app larger than 100Meg.
If you do wish to make a big Android app, first get the app working. But do not release any version of the app with a compileSkdVersion larger than 22. Later, I explain that the expansion libraries don't work with a later version so your final app must be version 22 or less. If you do release a later version the Google Store will not let you go back to version 22. In that case, abandon your app identifier and make a new app using version 22.
The libraries and code that you need can be obtained thru tools->android->SDK Manager". On the SDK Tools tab select and load Google Play APK Expansion library and Google Play Licensing Library. At the top of SDK Manager window note the Android SDK Location: field. After the download you can find these files in your SDK directory under extras/google. At this point the directories you are interested in are called market_apk_expansion and market_licensing.
Android documentation on the expansion files give you instructions for linking these libraries into your project. I have spent hours attempting make this work. Generally, Android Studio does bad things. First, it never seems to give error messages when what you enter is not right. Both of the times I got the expansion files to work I incorporated the libraries into my app and did not use the library features of Android Studio. However, putting the libraries into your app is not easy.
First, I should explain that these libraries are old and do not work with the latest SDK versions. I was able to get the libraries to work with a compileSdkVersion of 22. (See build.gradle for the module:app.) The targetSdkVersion should be 22. And under dependencies you should have compile 'com.android.support:appcompat-v7:22.2.1'. This will allow you to use the DisplayWebpageActivity.class. I think this was added in version 23 but it seems to work in this version of 22. I also have added compile 'com.google.android.gms:play-services-appindexing:8.1.0' but I do not know if this is necessary.
In my opinion there is no hope for ordinary people to write the code necessary to download an app. I used the code from the sample that is included in the "extras". You find this sample at /extras/google/market_apk_expansion/downloader_sample. What you need to do is to make this sample app (which does nothing but load the .obb files) be the opening page of your app. Add an Intent to the sample program so that it calls the real first page of your app when the .obb files are present.
The sample program consists of a "res" directory and three .java files. You need to put the three .java files into your main java directory. Change the name of the "res" directory (to "res2") and put this into the same directory that contains your normal "res" directory. To get Android Studio to recognize both "res" directories you add the following to your "build.gradle (Module:app)" file:
sourceSets {
main {
res.srcDirs = ['src/main/res']
res.srcDirs += [ 'src/main/res2']
res.srcDirs += [ 'src/main/res3']
}
}
The above is in the android { } section. The "res3" will come from the libraries.
All of the package names and some of the includes in the three .java sample files must be fixed to match your package name. In "res2" there will be an app name that conflicts with your app name, so delete it. You must fix your manifest so that it starts to execute SampleDownloaderActivity. And fix SampleDownloaderActivity so that it call the start of your app when the .obb files are there.
The Android documentation suggests that you make your .obb file a zip archive. I did this with my first expansion file app. For my application this did not work well. I had lots of small images. These images were already compressed (.jpg). Accessing any image was slow so I ended up making a poorly performing app.
In the second expansion app I used the "patch" file for a directory and put the photos into the "main" file. To access a photo the patch file is read and put into a dictionary that contains photo names, locations and lengths. This lets the app find where an image is in the main file, which is treated as a random access file. The photos that I need are copied into the storage for the app and given their proper names (xxx.jpg). The photos are then accessed inside of HTML code.
The two libraries are also integrated into the app. There is a "res" directory that is renamed to "res3" and put into the same directory as the regular "res" directory. In my app/src/main/java/com/developer/app_name directory I added a "downloader" and "licensing" directories. I wrote a program to move the libraries into their directories while fixing the package names and other occurrences of the original package name. The program only got some of the names that needed to be changed. Keep working on getting all these names fixed while ignoring some other kinds of errors. Many things that reference the "res3" directory did not work until I added an import statement like this:
import com.developer.app_name.R;
In the licensing library the LicenseChecker.java file contains code that will crash in SDK version 22. You must fix this code or use an older SDK version. Go to around line 150. Comment out the code that starts with boolean bindResult = mContext to Context.BIND_AUTO_CREATE);. Replace it with the following:
Intent serviceIntent = new Intent(
new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")));
serviceIntent.setPackage("com.android.vending");
There are a couple other errors in the library where Android Studio gives good suggestions on ways to fix them.
During debug you can manually put the .obb files into your device. On my Android this is done in Nexus 5/Internal storage/Android/obb where I make a folder called com.developer.app_name. I have discovered that what I see on my computer is not always what is in the device. Sometimes I must power the device off and on in order to see what is actually there. I spent lots of time trying to understand why the app could not find the .obb files when I could see them thru the computer. In fact, the .obb files were not there.
After you app works in debug with and without the .obb files, and without the .obb files it tells you that they can't be obtained from the store, it is time to take the app to production in order to finish testing it. If this is the first upload to the store the upload software fails to ask for the .obb files. So upload your app. Then, before attempting to release it, change the load number and version number and upload it a second time. This time it will ask for the .obb files.
Add them as a modules , Or import them in eclipse as a libraries ,go to Android studio and import non android studio project .. Go to Project Directory