I am trying to handle use cases where the user doesn't have Play set up correctly. It seems extremely straight forward, but I can't get the dialog to do anything. I have the following code in onCreate for a FragmentActivity that uses Maps v2. I have ensured that execution does go inside the if statements.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
this.helper = (DatabaseHelper) OpenHelperManager.getHelper(this, DatabaseHelper.class);
int googlePlayStatus = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(googlePlayStatus != ConnectionResult.SUCCESS) {
if(GooglePlayServicesUtil.isUserRecoverableError(googlePlayStatus)) {
GooglePlayServicesUtil.getErrorDialog(googlePlayStatus, this, googlePlayStatus).show();
}
}
//... do some stuff, such as use CameraUpdateFactory which throws NPE.
}
I have tried inserting the code into another activity, without the if checks, and the dialog shows just fine.
If a i am getting it right, you are trying to get the dialog error to be shown. Try this:
Edited:
Ok, i was having the same problem as you some time ago, so this is the exact code i use on one of my projects. I call the 'getActivity' on 'isGooglePlayServicesAvailable' instead of passing this.
int checkGooglePlayServices = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity());
if (checkGooglePlayServices != ConnectionResult.SUCCESS) {
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(checkGooglePlayServices, getActivity(), DIALOG_GET_GOOGLE_PLAY_SERVICES);
dialog.show();
}
Related
Because the Android SDK 23 gives users the possibility to deny apps access to certain functionalities I wanted to update one of my apps to request permissions as it is described in here: https://developer.android.com/preview/features/runtime-permissions.html.
In one of the activities I embed a SupportMapFragment. To make it work you need to have the WRITE_EXTERNAL_STORAGE permission, so I request it when I start the activity which results in a creation of a permission request dialog.
Now the problem is that when the dialog is still open and I rotate the device the activity will be restarted and open a new permission request dialog while the old one is still there. The result is two of those dialogs on top of each other and only one of it being useful.
Is there a way to get rid of the dialog that was started first?
As CommonsWare said in his comment the best solution is to put a boolean into the savedInstanceState-Bundle to know if the dialog is still open.
Example:
// true if dialog already open
private boolean alreadyAskedForStoragePermission = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
alreadyAskedForStoragePermission = savedInstanceState.getBoolean(STORAGE_PERMISSION_DIALOG_OPEN_KEY, false);
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY, alreadyAskedForStoragePermission);
}
private void checkStoragePermission(){
if(alreadyAskedForStoragePermission){
// don't check again because the dialog is still open
return;
}
if(ActivityCompat.checkSelfPermission(this, STORAGE_PERMISSIONS[0]) != PackageManager.PERMISSION_GRANTED){
// the dialog will be opened so we have to keep that in memory
alreadyAskedForStoragePermission = true;
ActivityCompat.requestPermissions(this, STORAGE_PERMISSIONS, STORAGE_PERMISSION_REQUEST_CODE);
} else {
onStoragePermissionGranted();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
switch (requestCode){
case STORAGE_PERMISSION_REQUEST_CODE:
// the request returned a result so the dialog is closed
alreadyAskedForStoragePermission = false;
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
onStoragePermissionGranted();
}
break;
}
}
As #user1991776 mentioned there is actually an undocumented extra that contains whether or not there is a permission dialog open at the moment, in Activity:
private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY =
"android:hasCurrentPermissionsRequest";
However there is a better way. When you request a permission dialog the second time (due to a rotation), Activity automatically cancels the old dialog by calling your onRequestPermissionResult() with empty arrays:
public final void requestPermissions(#NonNull String[] permissions, int requestCode) {
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can reqeust only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
Or course this behaviour isn't documented because this is Android, and who wants to document complex behaviour?
Anyway you can just always request permissions in onCreate() and then ignore calls to onRequestPermissionsResult() with zero-length permissions arrays.
I guess as this is a system dialog you cannot control it. You could instead prevent that your activity gets reloaded if you turn your device.
I am attempting to implement the AdMob with Google Play Services.
So far I have the default test banner appearing but I want to try some of the test ads.
I read that the emulator (AVD) must have Google APIs 16 or 17 as the target in order to test the AdMob however when I create a device that has this as target the emulator fails to load ( i left it for a good 20 minutes still has not loaded yet :( I just see the flashing android logo
This my AVD device
This is my AdFragment class that contains all the code related to the advertisements
public class AdFragment extends Fragment
{
private AdView mAdView;
#Override
public View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
// TODO Auto-generated method stub
return inflater.inflate(R.layout.fragment_ad, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
mAdView = (AdView)getView().findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
.build();
// Start loading the ad in the background.
mAdView.loadAd(adRequest);
}
/** Called when leaving the activity */
#Override
public void onPause()
{
if (mAdView != null)
{
mAdView.pause();
}
super.onPause();
}
/** Called when returning to the activity */
#Override
public void onResume()
{
super.onResume();
if (mAdView != null)
{
mAdView.resume();
}
}
/** Called before the activity is destroyed */
#Override
public void onDestroy() {
if (mAdView != null)
{
mAdView.destroy();
}
super.onDestroy();
}
}
Now im unsure whether it is my code that not generating the device ID or a problem with my created AVD device. The tutorials i seen have something like this
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
.addTestDevice("2EAB96D84FE62876379A9C030AA6A0AC")
Now i don't know if the last line is the code given by LogCat or is something that i just have to put in. I noticed the developer.google website has a different code so i assume i don't need to include in my code since i have not got it yet.
Please help. thank you.
UPDATE 1
I added this code inside On Resume inside my main activity
#Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
int isAvaiable = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(isAvaiable == ConnectionResult.SUCCESS)
{
Log.d("TEST", "GPS IS OK");
}
else if(isAvaiable == ConnectionResult.SERVICE_MISSING || isAvaiable == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED || isAvaiable == ConnectionResult.SERVICE_DISABLED)
{
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(isAvaiable, this, 1);
dialog.show();
}
}
The API to test banners should be 17 or higher. You have a good answer here that explains it. GPS in emulator.
For the problems related to launch the emulator the only suggestion I can give you is trying VM Acceleration and use an smaller screen.
You can try other emulators like x86Emulator and download and the last ISO versions 4.4. In my case the default android emulator took 10-12minutes to be responsive in a ldpi, with the other only 2 in a hdpi.
About the addTestDevice, I think AdRequest.DEVICE_ID_EMULATOR is enough, but if you see in the logcat the MD5 of your device ID then add this hash.
Last but not least, remember to check if GPS are installed at the beginning, it is explained on the DOCS.
To verify the Google Play services version, call isGooglePlayServicesAvailable(). If the result code is SUCCESS, then the Google Play services APK is up-to-date and you can continue to make a connection. If, however, the result code is SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED, or SERVICE_DISABLED, then the user needs to install an update.
So you will avoid errors.
I'm taking a look at this example
https://developer.android.com/training/location/retrieve-current.html#CheckServices
Here is the code in question:
public class MainActivity extends FragmentActivity {
...
private boolean servicesConnected() {
...
if (ConnectionResult.SUCCESS == resultCode) {
...
// Google Play services was not available for some reason.
// resultCode holds the error code.
} else {
// Get the error dialog from Google Play services
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
resultCode,
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
...
}
}
}
If we take a look at GooglePlayServicesUtil.getErrorDialog(..) we are passing a reference to this which happens to be an Activity.
The question is:
Would this cause a memory leak during a configuration change?
I guess the answer depends on how/if GooglePlayServicesUtil.getErrorDialog(..) keeps the reference to Activity internally.
Yep on my app it used to leak
If you have the Google play services error dialog up and then rotate again it will leak
This is the solution I put in place to solve the leak but this assumes your Google play services check is in onResume
public class MainActivity extends Activity
{
private Dialog googlePlayErrorDialog;
#Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
int isAvaiable = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(isAvaiable == ConnectionResult.SUCCESS)
{
Log.d("TEST", "GPS IS OK");
}
else if(isAvaiable == ConnectionResult.SERVICE_MISSING ||
isAvaiable == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED ||
isAvaiable == ConnectionResult.SERVICE_DISABLED)
{
googlePlayErrorDialog = GooglePlayServicesUtil.getErrorDialog(isAvaiable, this, 10);
googlePlayErrorDialog.show();
}
}
#Override
protected void onPause()
{
// TODO Auto-generated method stub
super.onPause();
if(googlePlayErrorDialog != null)
{
googlePlayErrorDialog.dismiss();
}
}
So the deal here is that I set the getErrorDialog equal to one my own dialog variable and then in onPause do a simple null check (to avoid dreaded null pointer exception!) and call dismiss.
I got the idea from reading this if you want more information
http://publicstaticdroidmain.com/2012/01/avoiding-android-memory-leaks-part-1/
What is the right context to pass to the isGooglePlayServicesAvailable(Context context) method?
You can pass anyone context like:
Activity context (By passing this) ,
Application context (by getting through getApplicationContext()),
Base context (By getting through getBaseContext()) etc.
actually isGooglePlayServicesAvailable() attached to whole application not an single activity that's why you can use anyone context object.
Hope this information will help you.
I just use the Activity context where users land when they start up my app, and that's worked (we've had it show up in testing here).
My code in that Activity is this:
private void checkPlayServices() {
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (status != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(status)) {
GooglePlayServicesUtil.getErrorDialog(status, this, 0).show();
} else {
ToastHelper.showCenteredToast("Until you update your Google Play Services, this app cannot run on this phone");
finish();
}
}
You can see on the first line of the function that I'm passing this into the check, and that just maps to an Activity.
I'm checking avalibility of google play services on device. I do it with these code:
final int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
final DialogInterface.OnCancelListener cancelListener = new DialogInterface.OnCancelListener() {
#Override
public void onCancel(final DialogInterface dialog) {
finish();
}
};
final Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
resultCode, this, GOOGLE_PLAY_SERVICES_REQUEST_CODE, cancelListener
);
errorDialog.show();
}
I get resultCode = 2 (it's mean that Google Play Services needs to update). Dialog is shown, but instead of text, I get paths to layout.
It's looks like there are some interference of resource in app and resource in PlaYServices lib. But how it's possible and how to avoid id?
Since the accepted answer is somewhat unclear, I'll leave a signpost with my conclusions (mostly extracted from the comments on the question) which I believe are correct.
Short version: It seems that resource ids were incorrectly generated for this app.
It's obvious that the Google Play Services dialog intended to show strings in those places. The getErrorDialog() method is implemented like this (obfuscated, but the meaning can still be understood):
...
case 1:
return localBuilder.setTitle(R.string.common_google_play_services_install_title).create();
case 3:
return localBuilder.setTitle(R.string.common_google_play_services_enable_title).create();
case 2:
return localBuilder.setTitle(R.string.common_google_play_services_update_title).create();
...
Also, mistakenly doing something like getResources().getString(R.layout.my_layout) will return a string with the name of the original resource file ("res/layout/my_layout.xml").
So, we can conclude that, for some reason, the value of the Play Services Library resource, say, com.google.android.gms.R.string.common_google_play_services_install_title is actually the same as for the resource R.layout.dialog_share in the application project.
This probably stems for an incorrect build process, or an incorrect usage of the Google Play Services library (for example, including its jar directly, without the corresponding library process).
I have tested the code with google play service library version 4452000(use version >= 4452000). The code is as follows:
public class MainActivity extends Activity{
ProgressBar pBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
final DialogInterface.OnCancelListener cancelListener = new DialogInterface.OnCancelListener() {
#Override
public void onCancel(final DialogInterface dialog) {
finish();
}
};
final Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
resultCode, this, 10, cancelListener
);
errorDialog.show();
}
}
}
Check your version of google play services and update if needed.
Here's the class I've been working on to check google play. It's not in production later this summer it will be so let me know with a comment if you have problems. It works tested on zten9120 and HTC EVO. The flow is like this. If static method isGooglePlay(context) returns false. Initialize the class and call the non static isgoogleplay() which will present the dialog to the user if googleplay services is not installed. The oncofigurationchange method handles when the device is rotated. onstop sets the class to null.
Modify three events in your activity.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!MyGooglePlay.isGooglePlay(getApplicationContext())) {
myGP = new MyGooglePlay(this);
myGP.isGooglePlay();
}
#Override
public void onStop() {
super.onStop();
myGP = null;
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (myGP != null) {
if (myGP.errorFragment.isVisible()) {
myGP.errorFragment.dismissAllowingStateLoss();
}
}
}
Here's the code which I keep in separate class
package com.gosylvester.bestrides.util;
import android.app.Dialog;
import android.content.Context;
import android.content.IntentSender;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
public class MyGooglePlay {
private static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 31502;
private ActionBarActivity activity;
private FragmentManager fragManager;
public MyGooglePlay(ActionBarActivity activity) {
this.activity = activity;
this.fragManager = activity.getSupportFragmentManager();
}
public static boolean isGooglePlay(Context context) {
return (GooglePlayServicesUtil.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS);
}
public boolean isGooglePlay() {
if (isGooglePlay(activity)) {
return true;
} else {
return checkGooglePlay();
}
}
private static final String DIALOG_ERROR = "dialog_error";
public ErrorDialogFragment errorFragment;
private boolean checkGooglePlay() {
int mIsGooglePlayServicesAvailable = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(activity);
switch (mIsGooglePlayServicesAvailable) {
case ConnectionResult.SUCCESS:
return true;
default:
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
mIsGooglePlayServicesAvailable, activity,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
// If Google Play services can provide an error dialog
if (errorDialog != null) {
// Create a new DialogFragment for the error dialog
errorFragment = ErrorDialogFragment.newInstance();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(fragManager, "LocationUpdates");
}
// case ConnectionResult.SERVICE_MISSING:
// case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
// case ConnectionResult.SERVICE_DISABLED:
// case ConnectionResult.SERVICE_INVALID:
// case ConnectionResult.DATE_INVALID:
}
return false;
}
public void dismissMe() {
DialogFragment frag = (DialogFragment) fragManager
.findFragmentByTag("LocationUpdates");
if (frag != null) {
frag.dismissAllowingStateLoss();
}
}
public static class ErrorDialogFragment extends DialogFragment {
// Global field to contain the error dialog
private Dialog mDialog;
static ErrorDialogFragment newInstance() {
ErrorDialogFragment d = new ErrorDialogFragment();
return d;
}
// Default constructor. Sets the dialog field to null
public ErrorDialogFragment() {
super();
mDialog = null;
}
// Set the dialog to display
public void setDialog(Dialog dialog) {
mDialog = dialog;
}
public void onPause(){
super.onPause();
this.dismissAllowingStateLoss();
}
// Return a Dialog to the DialogFragment.
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mDialog;
}
}
public void onConnectionFailed(ConnectionResult connectionResult) {
/*
* Google Play services can resolve some errors it detects. If the error
* has a resolution, try sending an Intent to start a Google Play
* services activity that can resolve error.
*/
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(activity,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (IntentSender.SendIntentException e) {
// Log the error
e.printStackTrace();
}
} else {
/*
* If no resolution is available, display a dialog to the user with
* the error.
*/
showErrorDialog(connectionResult.getErrorCode(), activity);
}
}
/* Creates a dialog for an error message */
private void showErrorDialog(int errorCode, ActionBarActivity activity) {
// Create a fragment for the error dialog
ErrorDialogFragment dialogFragment = new ErrorDialogFragment();
// Pass the error that should be displayed
Bundle args = new Bundle();
args.putInt(DIALOG_ERROR, errorCode);
dialogFragment.setArguments(args);
dialogFragment
.show(activity.getSupportFragmentManager(), "errordialog");
}
}
Good Luck with getting google play services installed.
public static boolean isGooglePlayServiceAvailable(Context context) {
boolean isAvailable = false;
int result = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
if (result == ConnectionResult.SUCCESS) {
Log.d(TAG, "Play Service Available");
isAvailable = true;
} else {
Log.d(TAG, "Play Service Not Available");
if (GooglePlayServicesUtil.isUserRecoverableError(result)) {
GooglePlayServicesUtil.getErrorDialog(result,
(Activity) context, PLAY_SERVICES_RESOLUTION_REQUEST)
.show();
} else {
Log.d(TAG, "Play Service Not Available");
GooglePlayServicesUtil.getErrorDialog(result,
(Activity) context, PLAY_SERVICES_RESOLUTION_REQUEST)
.show();
}
}
return isAvailable;
}
Updated answer : (as per GooglePlayServicesUtil.getErrorDialog is null)
If Google Play Services is not installed on the device, you may not be able to use the error dialog.
As per Rahim's comment in the above, you should only use the dialog if you have an "isUserRecoverableError" (his code):
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (status != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(status)) {
GooglePlayServicesUtil.getErrorDialog(status, this,
REQUEST_CODE_RECOVER_PLAY_SERVICES).show();
} else {
Toast.makeText(this, "This device is not supported.", Toast.LENGTH_LONG).show();
finish();
}
}
UPDATE - (from http://www.riskcompletefailure.com/2013/03/common-problems-with-google-sign-in-on.html)
One additional bug that surfaced quite a lot around the release of the latest version of Google Play Services was the onConnectionFailed method being called with a ConnectionResult which does not have a resolution, and has an error code of ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED.
As you might guess from the name, this indicates that the version of Google Play Services on the device is too low. Normally new versions will be updated automatically, but there is always a time delay in the roll out, so it is quite possible to get this error as updates are released.
You can handle this in the onConnectionFailed by calling getErrorDialog on GooglePlayServicesUtil, but the best way is to actually check whether Google Play Services is installed and up to date before even trying to connect. You can see a snippet of how to do this in the documentation.
So this is suggesting that this error (as you yourself say) should be recoverable, although note the clause that I have made bold. I am not convinced that this dialog would always be usable. Common sense suggests to me that this might depend on the version from which you were upgrading. So I recommend that you explicitly check that the error is marked as recoverable. If it is marked as recoverable then this looks like a bug in Google Play Services.
I found solution. I don't undertand why it's happening, but there is a solution.
I've built my project with maven and include a google play services framework as android library project.
Today, I've migrated to gradle and include dependecy go GPS with gradle and it solved my problem.