I am trying to implement Android In App Update API using AppUpdateManager. I first tried following the documentation, which if you are here, then you know that doesn't work.
So then I've read about a dozen tutorials and blog articles on how to get this to work. I'm about 90% there. For me after the user accepts the update, the update is downloaded, my listener detects that the download is complete, and I display my own message with a call back (most of the tutorials use a Toast at this point, but that shouldn't matter). If my user taps on "Restart" which is the label I gave the OK side of the message that I'm displaying, then I call appUpdateManager.completeUpdate().
At this point I get a screen from Google Play being displayed with some nice animation, and an Installing progress bar when that finishes, the Google screen disappears, and then my app is restarted.
Problem, is that it didn't get installed, I can tell from my app version that it is still the previous version, and since it is still the previous version, the appUpdateManager is recreated and when it checks UpdateAvailability.UPDATE_AVAILABLE, it finds (obviously) that there is an update available, and goes back through the whole processing, downloading it again and trying to restart the app again.
I have added some log messages and checked to see if I am getting RESULT_IN_APP_UPDATE_FAILED in the onActivityResult, but it is coming back fine.
Any help or suggestions anyone has, would be great.
I create a handle to appUpdateManager at the beginning of MainActivity, like this:
private AppUpdateManager appUpdateManager;
I also create a listener to monitor the install state as I am working towards a FLEXABLE AppUpdateType like:
private InstallStateUpdatedListener UPDATE_LISTENER = installState -> {
if (installState.installStatus() == DOWNLOADED) {
Log.d(TAG, "~UPDATE_LISTENER - installStatus is DOWNLOADED");
Utility.displayAlert(UPDATE_READY);
}
Log.d(TAG, "~Error code is: " + installState.installErrorCode());
Log.d(TAG, "~Package name is: " + installState.packageName());
};
In my onCreate, I create the appUpdateManager and register the download listener as well as add the addOnSuccessListener like so:
Log.d(TAG, "~updateAppIfAvailable");
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(this);
Log.d(TAG, "~registerListener");
appUpdateManager.registerListener(UPDATE_LISTENER);
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
// Checks that the platform will allow the specified type of update.
Log.d(TAG, "~updateAvailability is " + appUpdateInfo.updateAvailability());
if ((appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
// Request the update.
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
this,
MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
My onActivityResult looks like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "~onActivityResult");
if (requestCode == MY_REQUEST_CODE) {
switch (resultCode) {
case Activity.RESULT_OK:
Log.d(TAG, "~User approved update");
break;
case Activity.RESULT_CANCELED:
Log.d(TAG, "~User rejected update");
break;
case RESULT_IN_APP_UPDATE_FAILED:
Log.d(TAG, "~Update failed");
break;
}
}
}
onResume is:
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
Log.d(TAG, "~onResume appUpdateManager is calling startUpdateFlowForResult");
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
this,
MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
Finally when I display my alert (this could be a Toast if you wanted), I have both a Cancel and a Restart option with a message that says "Update download is complete."
If the user presses the "Restart" option, then it calls this updateAndRestart() method:
private void updateAndRestart() {
Log.d(TAG, "~updateAndRestart");
Log.d(TAG, "~Completing app update.");
if (appUpdateManager != null) {
appUpdateManager.completeUpdate();
Log.d(TAG, "~unregisterListener");
appUpdateManager.unregisterListener(UPDATE_LISTENER);
}
}
Ok, it's been a week, apparently I've stumped the entire SO community. :) Seriously though, I've been reading Android docs and tutorials for the past week and I have no other answers, so I'm going to just say the Google Update API just isn't ready for production.
I found an alternate solution. It is a combination of
Using the AppUpdater library as mentioned in the answer to this question.
If I get a callback that the user tapped Update, then I take them to my Play Store App listing, using this intent. Make sure you replace "com.example.android" with your own app id. This google doc page shows you how to do it.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://play.google.com/store/apps/details?id=com.example.android"));
intent.setPackage("com.android.vending");
startActivity(intent);
Related
I am facing this error while disabling Bluetooth. Googled it but did not find solution. Here is my broadcast receiver which invokes when bluetooth state is changed.
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Log.e(LOG_TAG, "Broadcast receiver - onReceive");
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
h.removeCallbacks(runnable);
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
Log.i(LOG_TAG, "***** BLE-Bluetooth is disabled");
Toast.makeText(getApplicationContext(), "Bluetooth is disabled", Toast.LENGTH_LONG).show();
Log.i(TAG, "***** mBeaconScanner "+ mBeaconScanner);
if(null != mBeaconScanner)
mBeaconScanner.scanLeDevice(false);
break;
case BluetoothAdapter.STATE_ON:
Log.i(LOG_TAG, "***** BLE-Bluetooth is enabled");
Toast.makeText(getApplicationContext(), "Bluetooth is enabled", Toast.LENGTH_LONG).show();
if(null != mBeaconScanner)
mBeaconScanner.scanLeDevice(true);
break;
}
}
}
};
In the above code I am trying to stop ble scanning while disabling the Bluetooth. In the above code method "mBeaconScanner.scanLeDevice(false);" redirects to :
Log.i(TAG, "***** Stopping BLE Scan for Android version " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (bluetoothLeScanner != null)
{
bluetoothLeScanner.stopScan(mScanCallback);
}
} else {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
In the above snippet I am getting Exception when calling bluetoothLeScanner.stopScan(mScanCallback) method.
NOTE: this issue is not reproducible in all the devices but nexus and its not very frequently reproducible as well for me.
Please suggest me if there is any solution for this.. Thanks in advance
The only solution I can suggest here is comment this code:-
//mBeaconScanner.scanLeDevice(false);
The way BLE scan works is pretty complicated at the radio level. There are tons of factors (like power, efficiency etc..) that are taken into consideration while arriving at the right scan window sleep time etc..
For you in this particular case when the BT adapter itself is turned OFF, trying to stop scan is an invalid operation as far as the radio is concerned. Am glad that Android FW throws this exception !! Just get rid of this code and may be do some internal app specific state transition (if you have to) or else just log and be done in case BluetoothAdapter.STATE_OFF:
In GPS based applications, it is important that the user enable his GPS. If not then usually we would show a dialog stating that the user "should enable his GPS from the settings to be able to use this functionality".
When the user press OK he will be redirected to the Settings page, I don't like this solution since it takes the user out of the application context in to the settings.
I have noticed that "google maps" application has a better solution, which is to show a neat dialog when a GPS feature is needed. Upon the user's selection "OK" GPS will be enabled directly without any redirection to the settings.
Can I enable the GPS without redirecting the user to the settings screen like in "google maps" app?
checkout the image below:
To have that feature you need:
First (at least) the version 7.0 of play services
compile 'com.google.android.gms:play-services-location:16.0.0'
Second something like this in your code (I had it in my onCreate):
-
// Check the location settings of the user and create the callback to react to the different possibilities
LocationSettingsRequest.Builder locationSettingsRequestBuilder = new LocationSettingsRequest.Builder()
.addLocationRequest(mLocationRequest);
PendingResult<LocationSettingsResult> result =
LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, locationSettingsRequestBuilder.build());
result.setResultCallback(mResultCallbackFromSettings);
And then create the callback:
// The callback for the management of the user settings regarding location
private ResultCallback<LocationSettingsResult> mResultCallbackFromSettings = new ResultCallback<LocationSettingsResult>() {
#Override
public void onResult(LocationSettingsResult result) {
final Status status = result.getStatus();
//final LocationSettingsStates locationSettingsStates = result.getLocationSettingsStates();
switch (status.getStatusCode()) {
case LocationSettingsStatusCodes.SUCCESS:
// All location settings are satisfied. The client can initialize location
// requests here.
break;
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
// Location settings are not satisfied. But could be fixed by showing the user
// a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
status.startResolutionForResult(
MapActivity.this,
REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException e) {
// Ignore the error.
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
Log.e(TAG, "Settings change unavailable. We have no way to fix the settings so we won't show the dialog.");
break;
}
}
};
And then, finally, in onActivityResult I had the following:
/**
* Used to check the result of the check of the user location settings
*
* #param requestCode code of the request made
* #param resultCode code of the result of that request
* #param intent intent with further information
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
//final LocationSettingsStates states = LocationSettingsStates.fromIntent(intent);
switch (requestCode) {
case REQUEST_CHECK_SETTINGS:
switch (resultCode) {
case Activity.RESULT_OK:
// All required changes were successfully made
if (mGoogleApiClient.isConnected() && userMarker == null) {
startLocationUpdates();
}
break;
case Activity.RESULT_CANCELED:
// The user was asked to change settings, but chose not to
break;
default:
break;
}
break;
}
}
I'm trying to use the DriveApi in order to create some folders and upload a text file with some data for a user.
I've tried implementing the quick-start guide from (link), but it has a few fundamental issues:
The api gets connected at onResume so the user will get prompted to give access to the app immediately after he opens the app which is confusing and scary.
If you deny or press the back button at the consent screen, the onResume method will get called again and the consent screen will be shown one more time, leading to an infinite loop.
I would rather like to connect the api when the user actually needs to store data so that will make more sense to the user. I tried doing it like this:
ResultCallback<DriveFolder.DriveFolderResult> folderCreatedCallback = new
ResultCallback<DriveFolder.DriveFolderResult>() {
#Override
public void onResult(#NonNull DriveFolder.DriveFolderResult result) {
clearCurrentAction();
if (!result.getStatus().isSuccess()) {
Log.e(TAG, "Error while trying to create the folder");
return;
}
Log.d(TAG, "Created a folder: " + result.getDriveFolder().getDriveId());
}
};
public DriveApiHelper(GoogleApiClient mGoogleApiClient) {
this.mGoogleApiClient = mGoogleApiClient;
}
public void createBackupsFolder() {
currentAction = DriveActions.CREATING_FOLDER;
if (mGoogleApiClient.isConnected()) {
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("test").build();
Drive.DriveApi.getRootFolder(mGoogleApiClient).createFolder(
mGoogleApiClient, changeSet).setResultCallback(folderCreatedCallback);
} else {
mGoogleApiClient.connect();
}
}
and this is how my onResume and onConnected methods look like:
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mDriveHelper = new DriveApiHelper(mGoogleApiClient);
}
//Log.d(TAG, mDriveHelper.getCurrentAction() + "");
Log.d("test", "Connected " + mGoogleApiClient.isConnected());
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected.");
switch (mDriveHelper.getCurrentAction()) { //resume the last action
case CREATING_FOLDER:
mDriveHelper.createBackupsFolder();
break;
}
}
I was hoping that keeping a reference of what the user tried to do when the api was asked to connect, I can resume that action after the api successfully connected. This is the closest implementation I've got to fit my needs, but after actually clicking the 'Allow' button from the consent screen none of the api callbacks gets called (onConnected, onConnectionFailed).
I actually need to call the connect method one more time in order to get connected and also fire the onConnected successfully resuming the users' action.
Turns out that I forgot about overriding onActivityResult (it wasn't mentioned in the documentation at that time and I don't know if they included it now)
Just add:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_CODE_RESOLUTION) {
mGoogleApiClient.connect();
}
} else {
if (requestCode == REQUEST_CODE_RESOLUTION) {
mDriveHelper.dismissStatusDialog();
}
}
}
I am trying to build an alarm application. When the alarm turns on, the user has to scan a matching QR code before it is turned off. I've taken a look at this link to get the sound playing: How to play ringtone/alarm sound in Android and I am using the ScanningViaIntent from the zxing library for the QR code scanner: https://code.google.com/p/zxing/.
So I start the sound in the onStart() activity:
#Override
public void onStart(){
super.onStart();
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
}
The user then starts the scanner by pressing a button:
private class HandleClick implements OnClickListener{
public void onClick(View arg0) {
IntentIntegrator integrator = new IntentIntegrator(AlarmRequirementsActivity.this);
integrator.initiateScan();
}
}
The result of the scanner is returned here:
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if (scanResult != null) {
System.out.println("scanREsult" + scanResult);
System.out.println("requestCode: " + requestCode);
TextView result =(TextView)findViewById(R.id.scanResult);
if (resultCode == RESULT_OK) {
String scanResultString = intent.getStringExtra("SCAN_RESULT");
if(scanResultString .equals(matchString))
{
result.setText("You found it!");
r.stop();
}
else
{
result.setText("\"" + scanResultString + "\""+ " did not match");
}
System.out.println(intent.getStringExtra("SCAN_RESULT"));
} else if (resultCode == RESULT_CANCELED) {
}
}
// else continue with any other code you need in the method
}
As you can see, I call r.stop() after a successful match. However these are my problems:
The activity is restarted after coming back from the scanner. It doesn't matter if the match was successful or not.
This results in two alarm tones being played now
I've tried putting it in the onCreate() method but to no avail as well.
UPDATE:
I've tried:
#Override
public void onStart(){
super.onStart();
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
r = RingtoneManager.getRingtone(getApplicationContext(), notification);
if(!r.isPlaying())
{
r.play();
}
}
and this below. Both of which with the same problems
if(scanResultString .equals(matchString))
{
result.setText("You found it!");
if(r.isPlaying())
{
r.stop();
}
}
The activity is restarted after coming back from the scanner. It doesn't matter if the match was successful or not.
I assume that you need to start another activity to do the scan, which means that your activity will (at least) need to be paused and more likely stopped to allow that other activity to run (as per the Android activity lifecycle).
Therefore, you will have to expect onStart() to be called when returning from the scanner.
This results in two alarm tones being played now
You should be able to avoid this and your code to check if the ringtone is already playing seems like a good start. However, I suspect you are creating a new ringtone object each time onStart() is executed.
It is hard for me to guess at all of the things you will need to do to fully resolve your problems (not to mention problems you will only see when your activity is fully recreated by Android - for example when the screen orientation changes - as this needs further handling in your code; see the Android doc for the activity lifecycle, particularly onSaveInstanceState()).
My guess at the next step would be to move the line:
r = RingtoneManager.getRingtone(getApplicationContext(), notification);
into your onCreate() method. My hope is that this, combined with the if (!r.isPlaying()) code should prevent the double-alarm issue in most cases.
I created my lockscreen application that trigerred by a SMS.. i have ListenSMS class that always listen for incoming SMS. Here's the code :
for (SmsMessage message : messages) {
String tempMessage[] = message.getDisplayMessageBody().toString().split(" ");
//checking command dan password
if (tempMessage[0].toString().equalsIgnoreCase("andro-lock") && tempMessage[1].toString().equals(tempPassword.toString())) {
//Toast.makeText(ListenSMSservice.this, "Menjalankan command andro-lock", Toast.LENGTH_LONG).show();
openDatabase();
updateStatusL();
Intent myIntent = new Intent(ListenSMSservice.this,LockScreenForm.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(myIntent);
}
else if (tempMessage[0].toString().equalsIgnoreCase("andro-unlock") && tempMessage[1].toString().equals(tempPassword.toString())) {
//Toast.makeText(ListenSMSservice.this, "Menjalankan command andro-unlock", Toast.LENGTH_LONG).show();
openDatabase();
updateStatusNL();
Intent myIntent = new Intent(ListenSMSservice.this,LockScreenForm.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle myKillerBundle = new Bundle();
myKillerBundle.putString("kill","1");
myIntent.putExtras(myKillerBundle);
getApplication().startActivity(myIntent);
}
}
If ListenSMS service has received an andro-lock command, it will go to the lockscreen.java and will go to the lockscreen.java with intent extra (putExtra) kill when it receive command andro-unclock. Here's my lockscreen.java:
public class LockScreenForm extends Activity implements OnClickListener {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.lockscreen);
Bundle extra = getIntent().getExtras();
if (extra == null) {
return;
}
//Toast.makeText(this, extra.getString("kill"), 1).show();
else if(this.getIntent().getExtras().getString("kill").equalsIgnoreCase("1")) {
try {
Toast.makeText(this, "extra accepted", 1).show();
finish();
} catch (Exception e) {
// TODO: handle exception
Toast.makeText(this, e.getMessage(), 1).show();
}
}
}
}
I want to close my locksreen.java when my ListenSMS service has received "andro-unlock" command, so I put extra on intent "kill" and check it in lockscreen.java. This lockscreen.java can check the extra intent and can display a toast "extra accepted" but can close the lockscreen activity with finish().
My assumption is for now that Intent.FLAG_ACTIVITY_NEW_TASK is duplicating a locksreen activity. So it will create a double lockscreen activity and the finish method is closing another lockscreen.java that started by Intent.FLAG_ACTIVITY_NEW_TASK. That's only assumption. Am i wrong? Please correct me.
Has anyone know how to solve my problem? I really want that "andro-unlock" command can close the lockscreen activity and need it works for my college final project. Please help.
From http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK:
When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.
I expect your problem is somewhere else. I'd suggest having the lockscreen Activity register a BroadcastReceiver, and then when the unlock message is received send an Intent that the BroadcastReceiver will catch. The Activity can then cleanly exit.
Try this: Intent.FLAG_ACTIVITY_NEW_TASK