How to exclude an Android App from Battery Optimization using code - java

I am new to Android, now I am working on a Project which is based on GPS. I got source code from internet(traccar). my requirement is like the app should update location on each 1Km or each 1hr. but the problem is the app not working in background after some time(10 - 20 mins). Is there any solution for this ?
what should I do(in code) to exclude this app from battery optimisation when the app is launching ? is it possible ?

I think you're having 2 different problems:
1) If you want to keep your app in background you should use a foreground Service. That way your app won't be considered to be in background by the system and the chances of its process being killed are reduced drastically. The downside is that as long as your Service is in foreground you need to show a permanent notification.
2) You cannot exclude your app from battery optimization yourself, but you can prompt the user the settings to whitelist your app. In order to do that you can refer to the official docs, you'll need to add the Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission to the manifest and then launch an intent with action ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS. The user will then be able to whitelist your app, only she/he can do that because otherwise every app would whitelist itself and the purpose of the battery optimization would be defied.

Add this permission in your manifest file Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
Request Permission at runtime inside onCreate method of your activity...
PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
String packageName = "org.traccar.client";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent i = new Intent();
if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
i.setData(Uri.parse("package:" + packageName));
startActivity(i);
}
}
This is the image of the code in debug mode:
This will be the view in app:
but the application not working as it is in No Restriction mode (background activity).
I want the application to work as No Restriction mode.

Add this permission in your manifest file
Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
Request Permission at runtime inside onCreate method of your activity...
private final int MY_PERMISSIONS_IGNORE_BATTERY_OPTIMIZATIONS =1;
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS),
MY_PERMISSIONS_IGNORE_BATTERY_OPTIMIZATIONS)
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
// Permission has already been granted
}

Here is the kotlin version of #Shafeeq Mohammed Answer and it worked for me. Thank you
fun checkBatteryOptimization(mContext: Context) {
val powerManager =
mContext.getSystemService(POWER_SERVICE) as PowerManager
val packageName = mContext.packageName
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val i = Intent()
if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
i.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
i.data = Uri.parse("package:$packageName")
mContext.startActivity(i)
}
}
}

Related

How to check whether in WiFi scan throttling is enable or disable?

I have a development scenario where I need to check whether WiFi throttling option is enable or disable?
If it is enable than I want to disable it programmatically.
private fun prepareForWiFiScan() {
Toast.makeText(this, "Wifi scan preparation started", Toast.LENGTH_SHORT).show()
circularProgressbar.visibility = View.VISIBLE
wiFiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
if (!wiFiManager.isWifiEnabled) {
wiFiManager.isWifiEnabled = true
}
wiFiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(this#MainActivity, "Wifi scan finished", Toast.LENGTH_SHORT).senter code herehow()
val i'sSuccess: Boolean = when {
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M -> intent.getBooleanExtra(
WifiManager.EXTRA_RESULTS_UPDATED,
false
)
else -> intent.getBooleanExtra(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, false)
}
circularProgressbar.visibility = View.GONE
when (isSuccess) {
true -> scanSuccess()
false -> scanFail()
}
}
}
registerReceiverAndStartScan()
}
private fun registerReceiverAndStartScan() {
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
registerReceiver(wiFiScanReceiver, intentFilter)
val success = wiFiManager.startScan()
if (!success) {
scanFail()
}
}
There are no way present until android 10 where we can check whether WiFi scan throttle is enable or disable. However in android R the new method added to check whether WiFi Scan throttling is enable or disable.
Please refer following link.
https://developer.android.com/reference/android/net/wifi/WifiManager#isScanThrottleEnabled()
Actually you can also check the wifi throttle in Android versions < 10 by using
Settings.Global.getInt(this.getContentResolver(), "wifi_scan_throttle_enabled")
It returns 1 if wifi throttle is enabled, 0 otherwise
You have to use a broadcastReceiver for receiving this. Check here
https://developer.android.com/guide/topics/connectivity/wifi-scan
This works via a root command shell in android 11. Returns false if "Wi-Fi scan throttling" is disabled in developer options. Wish there was an easier way to set this value via a command vs having to set it via multiple presses of the android GUI.
dumpsys wifi | grep wifi_scan_throttle_enabled

Fix the 'background service' problem on Android 8+

I have code running a service behind the scenes. It is set to run when we copy the text to the phone.
This code works fine on Android 8 below
But the problem is when I run the app on Android 8 and above
In my searches, I realized that I had to use FOREGROUND_SERVICEs and give specific access to the project.
What solutions do you suggest now?
Service Class:
public class AutoDownloadService extends Service {
private ClipboardManager mClipboardManager;
public static final String CHANNEL_ID = "ForegroundServiceChannel";
#Override
public void onCreate() {
super.onCreate();
mClipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
mClipboardManager.addPrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, SettingsActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
// stopSelf();
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
if (mClipboardManager != null) {
mClipboardManager.removePrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
private ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener =
new ClipboardManager.OnPrimaryClipChangedListener() {
#Override
public void onPrimaryClipChanged() {
ClipData clip = mClipboardManager.getPrimaryClip();
String textClipBoard = clip.getItemAt(0).getText().toString();
Toast.makeText(AutoDownloadService.this, textClipBoard, Toast.LENGTH_SHORT).show();
}
};
}
Manifest
<service
android:name=".services.AutoDownloadService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
and add finally uses permission
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
I think you should use Intent Service instead of service.
what you can do is if system shutdown your service you can again trigger it after sometime using alarm manger.
As stated in documentation
While an app is in the foreground, it can create and run both
foreground and background services freely. When an app goes into the
background, it has a window of several minutes in which it is still
allowed to create and use services. At the end of that window, the app
is considered to be idle. At this time, the system stops the app's
background services, just as if the app had called the services'
Service.stopSelf() methods.
So, you solution is to run foreground service on Android >= 8.0 and do something like this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(Intent(this, AutoDownloadService.class))
} else {
startService(Intent(this, AutoDownloadService.class))
}
}
First of all, you should not do this.
Monitoring clipboard in background is not something right.
Android 8 added some protection on this, so you should run as foreground services, to let the end user aware your app is monitoring the clipboard.
Anyway clipboard access only available to default IME from Android 10. So, your apps will not work in Android 10.
This example of my code is currently working fine but I have problems with Chinese mobile
Tested on mobile: Xiaomi 7
public class AutoDownloadService extends IntentService {
private ClipboardManager mClipboardManager;
public AutoDownloadService() {
super("AutoDownloadService");
}
#Override
public void onCreate() {
super.onCreate();
mClipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
mClipboardManager.addPrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
#Override
protected void onHandleIntent(Intent intent) {
Toast.makeText(this, " service started.", Toast.LENGTH_SHORT).show();
}
private final ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener =
new ClipboardManager.OnPrimaryClipChangedListener() {
#Override
public void onPrimaryClipChanged() {
ClipData clip = mClipboardManager.getPrimaryClip();
String textClipBoard = clip.getItemAt(0).getText().toString();
if (textClipBoard.startsWith("https://www.instagram.com/")) {
Toast.makeText(AutoDownloadService.this, textClipBoard, Toast.LENGTH_SHORT).show();
}
}
};
}
And in the file Manifast.xml This is how it is defined
<service android:name="com.amirhf.inatasave.services.AutoDownloadService" />
When you swipe the app from recent apps, few Chinese OEMs force stop the apps.
Once the App is force stopped, you can't post notifications, start services, receive broadcasts etc.
The only work around is : Your app should be whitelisted, added in
auto start list. Apps like WhatsApp, Facebook etc are already added by
the OEMs in the list.
This blog talks about similar problem of not receiving notifications when App is force stopped. https://hackernoon.com/notifications-in-android-are-horribly-broken-b8dbec63f48a
You can take a similar approach where you can have a "Troubleshoot" section where you can educate the user to add your app in auto start list.
If by any chance your app is very popular you can get in touch with
Chinese Manufacturer and request them to get your app whitelisted but
they do it for very popular apps. For example in my experience
Microsoft and Hike Messenger got it done for their apps.
I didn't quite understand if you're messing with file download or anything else. But I guess you're not going the right way. So here's what I may share.
From https://developer.android.com/training/scheduling/wakelock
If your app is performing long-running HTTP downloads, consider using DownloadManager.
If your app is synchronizing data from an external server, consider creating a sync adapter.
If your app relies on background services, consider using JobScheduler or Firebase Cloud Messaging to trigger these services
at specific intervals.
Also note that if you just have a task that has to done often, use JobIntentService. Its compatible with Oreo and versions below it:
Helper for processing work that has been enqueued for a job/service.
When running on Android O or later, the work will be dispatched as a
job via JobScheduler.enqueue. When running on older versions of the
platform, it will use Context.startService.
On Oreo and above versions there are limitations helping the device save resources (battery, ram...) and even when using JobIntentService you must consider them; otherwise your app may be recognized as Battery Draining App.
If what you're about to do is heavy and is important enough to be shown in notification bar, do it using ForegroundService. So that it will be taken more seriously by android system and chances of it being killed gets fewer.
Try using WorkManager it's a JetPack Library.
Advantages:
Ensures task execution, even if the app or device restarts (Guaranteed Execution)
You don’t need to write device logic to figure out what capabilities the device has and choose an appropriate API; instead, you can just hand your task off to WorkManager and let it choose the best option. It is a wrapper on all the above concepts.
Uses JobScheduler on devices with API 23+
Uses a combination of BroadcastReceiver + AlarmManager on devices with API 14-22
Ref : WorkManager Docs
Ref : Medium Article
Ref : Medium Article(1)
[Update] - stable version is out WorkManager

How to programmatically grant the "draw over other apps" permission in android?

How can I programmatically grant the permission in Settings -> Apps -> Draw over other apps in Android? I want to use system alert window but unable to in Android Marshmallow without forcing the user to go through the Settings app and grant the permission first.
You can check and ask for overlay permission to draw over other apps using this
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 0);
}
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 0);
}
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Here's the code for automatic granting the SYSTEM_ALERT_WINDOW permission to the package. To run this code, your Android application must be system (signed by platform keys).
This method is based on the following Android source code files: AppOpsManager.java and DrawOverlayDetails.java, see the method DrawOverlayDetails.setCanDrawOverlay(boolean newState).
#TargetApi(Build.VERSION_CODES.KITKAT)
public static void autoSetOverlayPermission(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
int uid = 0;
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
uid = applicationInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return;
}
AppOpsManager appOpsManager = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
final int OP_SYSTEM_ALERT_WINDOW = 24;
try {
Class clazz = AppOpsManager.class;
Method method = clazz.getDeclaredMethod("setMode", int.class, int.class, String.class, int.class);
method.invoke(appOpsManager, OP_SYSTEM_ALERT_WINDOW, uid, packageName, AppOpsManager.MODE_ALLOWED);
Log.d(Const.LOG_TAG, "Overlay permission granted to " + packageName);
} catch (Exception e) {
Log.e(Const.LOG_TAG, Log.getStackTraceString(e));
}
}
}
The code has been tested in Headwind MDM project, it successfully grants "Draw over other apps" permission without any user consent to the Headwind Remote application (disclaimer: I'm the project owner of Headwind MDM and Headwind Remote), when Headwind MDM application is signed by platform keys. The code has been tested on Android 10 (LineageOS 17).
Check this question and the answer:
SYSTEM_ALERT_WINDOW - How to get this permission automatically on Android 6.0 and targetSdkVersion 23
"Every app that requests the SYSTEM_ALERT_WINDOW permission and that is installed through the Play Store (version 6.0.5 or higher is required), will have granted the permission automatically."

Android targetSdkVersion 23 checkSelfPermission method

I am facing checking permission value in Android 6.0 (API 23). Always get 0 value even permission enable or disable from app's settings.
Below is step which I taken.
Contact permission manually disable from device settings->apps-> My apps-> permission -> Disable contact permission.
Still In Android 6.0 every time got 0 value when execute below line of code.
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
Below is my code. which I define in main launcher activity class
// Identifier for the permission request
private static final int WRITE_CONTACTS_PERMISSIONS_REQUEST = 9;
........
#Override
public void onCreate(Bundle savedInstanceState) {
....
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.LOLLIPOP_MR1)
{
sharedPreferencesEditor.putBoolean(getString(R.string.ALLOW_ACCESS_PHONEBOOK), true);
sharedPreferencesEditor.commit();
}
else {
getPermissionToReadUserContacts();
}
....
}
// Called when the user is performing an action which requires the app to read the
// user's contacts
public void getPermissionToReadUserContacts() {
// 1) Use the support library version ContextCompat.checkSelfPermission(...) to avoid
// checking the build version since Context.checkSelfPermission(...) is only available
// in Marshmallow
// 2) Always check for permission (even if permission has already been granted)
// since the user can revoke permissions at any time through Settings
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// The permission is NOT already granted.
// Check if the user has been asked about this permission already and denied
// it. If so, we want to give more explanation about why the permission is needed.
if (shouldShowRequestPermissionRationale(
Manifest.permission.WRITE_CONTACTS)) {
// Show our own UI to explain to the user why we need to read the contacts
// before actually requesting the permission and showing the default UI
}
// Fire off an async request to actually get the permission
// This will show the standard permission request dialog UI
requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS},
WRITE_CONTACTS_PERMISSIONS_REQUEST);
}
}
// Callback with the request from calling requestPermissions(...)
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String permissions[],
#NonNull int[] grantResults) {
// Make sure it's our original READ_CONTACTS request
if (requestCode == WRITE_CONTACTS_PERMISSIONS_REQUEST) {
if (grantResults.length == 1 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Write Contacts permission granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Write Contacts permission denied", Toast.LENGTH_SHORT).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
If you target SDK 23 (Android 6), all of the permissions (in your manifest) are disabled by default, whereas if your targetSDK is 22 (Android 5.1) and your app is running on Android 6, all of the permissions are enabled by default when the user installs the app, and even if the user revokes the permissions later on, checkSelfPermission returns incorrect value of PERMISSION_GRANTED
It is also available in the documentation of PermissionChecker
In the new permission model permissions with protection level dangerous are runtime permissions. For apps targeting M and above the user may not grant such permissions or revoke them at any time. For apps targeting API lower than M these permissions are always granted as such apps do not expect permission revocations and would crash. Therefore, when the user disables a permission for a legacy app in the UI the platform disables the APIs guarded by this permission making them a no-op which is doing nothing or returning an empty result or default error.
I had the same issue but I realized that there were locals modules with target version below 23. After removing them, the bug was resolved.

Prompt Android App User to Update App if current version <> market version

Lets say my Android App version 0.1 is installed currently on the User's phone. Everytime they launch my App I want to check if there is a different version available in the Android Market let's say this version is 0.2. If there is a mismatch between these two version I want to show a dialog box prompting the user to Upgrade the App.
I totally understand there exists a notification procedure from Android Market itself to the users but as far as my Analytics data is concerned it is not very effective in reminding users to upgrade to the new version of the App.
Any insight would be very helpful. Thanks StackOverflowers, you guys rock!
As of 2019 the best way for updating your app is to use In-app updates provided by Play Core library (1.5.0+). It works for Lollipop and newer, but let's be fair, Kit-Kat is less than 7% as of today and soon will be gone forever. You can safely run this code on Kit-Kat without version checks, it won't crash.
Official documentation: https://developer.android.com/guide/app-bundle/in-app-updates
There are two types of In-app updates: Flexible and Immediate
Flexible will ask you nicely in a dialog window:
whereas Immediate will require you to update the app in order to continue using it with full-screen message (this page can be dismissed):
Important: for now, you can't choose which type of update to roll out in your App Release section on Developer Play Console. But apparently, they will give us that option soon.
From what I've tested, currently, we're getting both types available in onSuccessListener.
So let's implement both types in our code.
In module build.gradle add the following dependency:
dependencies {
implementation 'com.google.android.play:core:1.6.1'//for new version updater
}
In MainActivity.class:
private static final int REQ_CODE_VERSION_UPDATE = 530;
private AppUpdateManager appUpdateManager;
private InstallStateUpdatedListener installStateUpdatedListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkForAppUpdate();
}
#Override
protected void onResume() {
super.onResume();
checkNewAppVersionState();
}
#Override
public void onActivityResult(int requestCode, final int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
switch (requestCode) {
case REQ_CODE_VERSION_UPDATE:
if (resultCode != RESULT_OK) { //RESULT_OK / RESULT_CANCELED / RESULT_IN_APP_UPDATE_FAILED
L.d("Update flow failed! Result code: " + resultCode);
// If the update is cancelled or fails,
// you can request to start the update again.
unregisterInstallStateUpdListener();
}
break;
}
}
#Override
protected void onDestroy() {
unregisterInstallStateUpdListener();
super.onDestroy();
}
private void checkForAppUpdate() {
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(AppCustom.getAppContext());
// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
// Create a listener to track request state updates.
installStateUpdatedListener = new InstallStateUpdatedListener() {
#Override
public void onStateUpdate(InstallState installState) {
// Show module progress, log state, or install the update.
if (installState.installStatus() == InstallStatus.DOWNLOADED)
// After the update is downloaded, show a notification
// and request user confirmation to restart the app.
popupSnackbarForCompleteUpdateAndUnregister();
}
};
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
// Request the update.
if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(installStateUpdatedListener);
// Start an update.
startAppUpdateFlexible(appUpdateInfo);
} else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) ) {
// Start an update.
startAppUpdateImmediate(appUpdateInfo);
}
}
});
}
private void startAppUpdateImmediate(AppUpdateInfo appUpdateInfo) {
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MainActivity.REQ_CODE_VERSION_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
private void startAppUpdateFlexible(AppUpdateInfo appUpdateInfo) {
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MainActivity.REQ_CODE_VERSION_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
unregisterInstallStateUpdListener();
}
}
/**
* Displays the snackbar notification and call to action.
* Needed only for Flexible app update
*/
private void popupSnackbarForCompleteUpdateAndUnregister() {
Snackbar snackbar =
Snackbar.make(drawerLayout, getString(R.string.update_downloaded), Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.restart, new View.OnClickListener() {
#Override
public void onClick(View view) {
appUpdateManager.completeUpdate();
}
});
snackbar.setActionTextColor(getResources().getColor(R.color.action_color));
snackbar.show();
unregisterInstallStateUpdListener();
}
/**
* Checks that the update is not stalled during 'onResume()'.
* However, you should execute this check at all app entry points.
*/
private void checkNewAppVersionState() {
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
//FLEXIBLE:
// If the update is downloaded but not installed,
// notify the user to complete the update.
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdateAndUnregister();
}
//IMMEDIATE:
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
startAppUpdateImmediate(appUpdateInfo);
}
});
}
/**
* Needed only for FLEXIBLE update
*/
private void unregisterInstallStateUpdListener() {
if (appUpdateManager != null && installStateUpdatedListener != null)
appUpdateManager.unregisterListener(installStateUpdatedListener);
}
And we're done!
Testing.
Please read the docs so you will know how to test it properly with test tracks on Google Play.
Long story short:
Sign your app with the release certificate and upload it to the one of publishing tracks in Developer Play Console under App Releases (alpha/beta/other custom closed track).
In your release track page in the Manage Testers section create and add a list of testers and make sure you checked the checkbox! - this step is optional since your developer account email is also a testers account and you can use it for testing.
Under the list of testers you will find "Opt-in URL" - copy this url and give it to your testers or open it yourself. Go to that page and accept proposition for testing. There will be a link to the app. (You won't be able to search for the app in Play Store so bookmark it)
Install the app on your device by that link.
In build.gradle increment the version of defaultConfig { versionCode k+1 } and build another signed apk Build > Generate Signed Bundle / APK... and upload it to your publishing track.
Wait for... 1 hour? 2 hours? or more before it will be published on the track.
CLEAR THE CACHE of Play Store app on your device. The problem is that Play app caches details about installed apps and their available updates so you need to clear the cache. In order to do that take two steps:
7.1. Go to Settings > App > Google PLay Store > Storage > Clear Cache.
7.2. Open the Play Store app > open main menu > My apps & games > and there you should see that your app has a new update.
If you don't see it make sure that your new update is already released on the track (go to your bookmarked page and use it to open your apps listing on the Play Store to see what version is shown there). Also, when your update will be live you'll see a notification on the top right of your Developer Play Console (a bell icon will have a red dot).
Hope it helps.
The Android Market is a closed system and has only an unofficial api that might break at any point of time.
Your best bet is simply to host a file(xml, json or simple text) on a web server of yours in which you just have to update the current version of your app when you post it on the Market.
Your app will then only have to fetch that file at startup, checks wether currently installed app has a lower version number and displays a dialog to warn the user he is lagging.
Another option you can use, if you want to avoid having your backend server to store your current app version like it's suggested in the accepted answer, is to use Google Tag Manager (GTM).
If you're already using the Google Analytics SDK, you have the GTM in it also.
In GTM you can define a value in the container for your app that specifies your latest released version. For example:
{
"latestAppVersion": 14,
...
}
Then you can query that value when your app starts and show the user update dialog reminder if there's a newer version.
Container container = TagManager.getInstance(context).openContainer(myContainerId);
long latestVersionCode = container.getLong("latestAppVersion");
// get currently running app version code
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
long versionCode = pInfo.versionCode;
// check if update is needed
if(versionCode < latestVersionCode) {
// remind user to update his version
}
Take a look at this library that you can use to query the Android Market API
http://code.google.com/p/android-market-api/
You can use this Android Library: https://github.com/danielemaddaluno/Android-Update-Checker. It aims to provide a reusable instrument to check asynchronously if exists any newer released update of your app on the Store.
It is based on the use of Jsoup (http://jsoup.org/) to test if a new update really exists parsing the app page on the Google Play Store:
private boolean web_update(){
try {
String curVersion = applicationContext.getPackageManager().getPackageInfo(package_name, 0).versionName;
String newVersion = curVersion;
newVersion = Jsoup.connect("https://play.google.com/store/apps/details?id=" + package_name + "&hl=en")
.timeout(30000)
.userAgent("Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")
.referrer("http://www.google.com")
.get()
.select("div[itemprop=softwareVersion]")
.first()
.ownText();
return (value(curVersion) < value(newVersion)) ? true : false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
And as "value" function the following (works if values are beetween 0-99):
private long value(String string) {
string = string.trim();
if( string.contains( "." )){
final int index = string.lastIndexOf( "." );
return value( string.substring( 0, index ))* 100 + value( string.substring( index + 1 ));
}
else {
return Long.valueOf( string );
}
}
If you want only to verify a mismatch beetween versions, you can change:
"value(curVersion) < value(newVersion)" with "value(curVersion) != value(newVersion)"
For prompting Android App User to Update App if current version is not equal to market version, you should first check the app version on the market and compare it with the version of the app on the device. If they are different, it may be an update available. In this post I wrote down the code for getting the current version of market and current version on the device and compare them together. I also showed how to show the update dialog and redirect the user to the update page. Please visit this link: https://stackoverflow.com/a/33925032/5475941
My working Kotlin code for force App update:
const val FLEXIABLE_UPADTE: Int = 101
const val FORCE_UPDATE: Int = 102
const val APP_UPDATE_CODE: Int = 500
override fun onCreate {
// Get updateType from Webservice.
updateApp(updateType)
}
private fun updateApp(statusCode: Int) {
appUpdateManager = AppUpdateManagerFactory.create(this #MainActivity)
val appUpdateInfoTask = appUpdateManager ? .appUpdateInfo
appUpdateInfoTask ? .addOnSuccessListener {
appUpdateInfo - >
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
if ((statusCode == Constants.FORCE_UPDATE))
appUpdateManager ? .startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.IMMEDIATE, this, Constants.APP_UPDATE_CODE
)
else if (statusCode == Constants.FLEXIABLE_UPADTE)
appUpdateManager ? .startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.FLEXIBLE, this, Constants.FLEXIABLE_UPADTE
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent ? ) {
try {
if (requestCode == Constants.APP_UPDATE_CODE && resultCode == Activity.RESULT_OK) {
if (resultCode != RESULT_OK) {
appUpdateCompleted()
}
}
} catch (e: java.lang.Exception) {
}
}
private fun appUpdateCompleted() {
Snackbar.make(
findViewById(R.id.activity_main_layout),
"An update has just been downloaded.",
Snackbar.LENGTH_INDEFINITE
).apply {
setAction("RESTART") {
appUpdateManager.completeUpdate()
}
setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
show()
}
}

Categories

Resources