I struggle to ask ACCESS_FINE_LOCATION on a XIAOMI REDMI NOTE 4 which is running Android 6.0.
I tried to use geocoder and location. It works on iphone and a samsung on Android 8 (don't know the name).
TL;DR : the permission is never ask and not visible in app settings and Xiaomi.
Here is my code:
Android
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import java.util.Arrays;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL_GPS = "runtimepermission/access_fine_location";
private PermissionCallback getAccessFineLocationPermissionCallback;
private boolean rationaleJustShown = false;
private static final int GET_ACCESS_FINE_LOCATION_PERMISSION_REQUEST_ID = 1234;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL_GPS).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, final MethodChannel.Result result) {
getAccessFineLocationPermissionCallback = new PermissionCallback() {
#Override
public void granted() {
rationaleJustShown = false;
result.success(0);
}
#Override
public void denied() {
rationaleJustShown = false;
result.success(1);
}
};
if (call.method.equals("hasPermission")) {
askPermission();
}
}
});
}
private void askPermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
GET_ACCESS_FINE_LOCATION_PERMISSION_REQUEST_ID);
}
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
System.out.println("requestCode:" + requestCode);
for (int grantResult : grantResults) {
System.out.println("grandResult[i]:" + grantResult);
}
switch (requestCode) {
case GET_ACCESS_FINE_LOCATION_PERMISSION_REQUEST_ID:
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getAccessFineLocationPermissionCallback.granted();
} else {
getAccessFineLocationPermissionCallback.denied();
}
return;
}
}
public interface PermissionCallback {
void granted();
void denied();
}
Flutter:
static const _methodChannel = const MethodChannel('runtimepermission/access_fine_location');
Future<PermissionState> canGetLocation() async {
try {
final int result = await _methodChannel.invokeMethod('hasPermission');
return new Future.value(PermissionState.values.elementAt(result));
} on PlatformException catch (e) {
print('Exception ' + e.toString());
}
return new Future.value(PermissionState.DENIED);
}
It throws a PlatformException with an error message: The user explicitly denied the use of location services for this app or location services are currently disabled in Settings.
On the phone, the popup never shows up, and the permission is not displayed in settings > installed app > my app > permissions
Can you help me ?
Thanks.
Cache problem.
If you're experiencing odd problems like I had, clean the cache :)
Had the same problem. I was able to solve mine by enabling the app to auto init and dont optimize battery. It seems to be a xiaomi only problem with no plugins or flutter dev solution so far. My recommendation is to make a screen teaching the user what to do and redirect them
Related
I'm looking for some guidance related to accessing Google Drive with an Android app.
1) I need to be able to read files uploaded by users outside of my app. Does this mean I need full-drive access? (If the app could create a folder and then see all files uploaded by the user that exist in this folder, that would be great, but I don't think it works this way.)
2) If I need full-drive access, it seems that Googles "Drive API for Android" doesn't support this, and I need to use the REST api. I think this is true.
3) I need an Auth 2.0 client ID from Google. If I use the rest API, does this mean I need to use a "Web Application" ID? I think I need this because I want an "auth code". I wasn't able to get it working with an "Android" type ID.
4) I'm currently using "Google Sign-In" for Android to handle the login and provide an auth code. I can then convert this into a Token + Refresh Token, and save these so I can get new tokens after an hour in some fashion. Is this manually handling of refresh tokens required?
It's getting ugly, but I think that since I need (?) full-drive access then this is the procedure.
Thanks for any guidance.
Edit: The question has been identified as a duplicate. The link provided gives an answer for question #2, but doesn't address the other questions.
I agree the question is messy...
I'm answering my own question.
I struggled with this because A) Google's REST example uses an outdated login process, B) The "Sign In" example uses code that doesn't work with "full-access" scope, and C) the there were too many vastly different code examples when trying to put it all together.
To quickly answer my questions as I see it now:
1) Yes, full-drive access is required to read files uploaded outside my app.
2) Yes, I need to use REST api.
3) Yes, I need a "Web Application" client ID.
4) Google Sign-In seems the best way currently to sign in, and using a GoogleCredential object along with the Drive api abject will handle the token refreshes automatically, as long as you keep around a refresh token.
In case anyone else is struggling with accessing Drive with full-access from Android using the latest "Sign-In" procedure and REST v3, below is my sample code.
In addition to the "Web application" OAuth client ID, you also need to create an "Android" type ID with a matching package name and certificate fingerprint in order for the Sign-In to work. Also note that you'll have different certificates for your dev and production versions. The IDs/codes from these Android clients do not need to be entered into the app.
build.gradle : app
// Google Sign In
compile 'com.google.android.gms:play-services-auth:10.0.1'
// Drive REST API
compile('com.google.apis:google-api-services-drive:v3-rev54-1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
Activity
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Callback from Signin (Auth.GoogleSignInApi.getSignInIntent)
if (requestCode == 1) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
_googleApi.handleSignInResult(result);
}
}
A "GoogleApi" class to do the work
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class GoogleApi implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private Context _context;
private Handler _handler;
private GoogleCredential _credential;
private Drive _drive;
private GoogleApiClient _googleApiClient; // only set during login process
private Activity _activity; // launch intent for login (UI)
// Saved to data store
private boolean _loggedIn;
private String _refreshToken; // store, even if user is logged out as we may need to reuse
private static final String ClientID = "xxxxxx.apps.googleusercontent.com"; // web client
private static final String ClientSecret = "xxxxx"; // web client
private class FileAndErrorMsg {
public File file;
public String errorMsg;
public FileAndErrorMsg (File file_, String errorMsg_) { file = file_; errorMsg = errorMsg_; }
}
private class FileListAndErrorMsg {
public List<File> fileList;
public String errorMsg;
public FileListAndErrorMsg (List<File> fileList_, String errorMsg_) { fileList = fileList_; errorMsg = errorMsg_; }
}
// -------------------
// Constructor
// -------------------
public GoogleApi (Context context) {
_context = context;
_handler = new Handler();
loadFromPrefs(); // loggedIn, refreshToken
// create credential; will refresh itself automatically (in Drive calls) as long as valid refresh token exists
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
_credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(ClientID, ClientSecret) // .addRefreshListener
.build();
_credential.setRefreshToken(_refreshToken);
// Get app name from Manifest (for Drive builder)
ApplicationInfo appInfo = context.getApplicationInfo();
String appName = appInfo.labelRes == 0 ? appInfo.nonLocalizedLabel.toString() : context.getString(appInfo.labelRes);
_drive = new Drive.Builder(transport, jsonFactory, _credential).setApplicationName(appName).build();
}
// -------------------
// Auth
// -------------------
// https://developers.google.com/identity/sign-in/android/offline-access#before_you_begin
// https://developers.google.com/identity/sign-in/android/offline-access#enable_server-side_api_access_for_your_app
// https://android-developers.googleblog.com/2016/02/using-credentials-between-your-server.html
// https://android-developers.googleblog.com/2016/05/improving-security-and-user-experience.html
public boolean isLoggedIn () {
return _loggedIn;
}
public void startAuth(Activity activity) {
startAuth(activity, false);
}
public void startAuth(Activity activity, boolean forceRefreshToken) {
_activity = activity;
_loggedIn = false;
saveToPrefs();
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(new Scope("https://www.googleapis.com/auth/drive"))
.requestServerAuthCode(ClientID, forceRefreshToken) // if force, guaranteed to get back refresh token, but will show "offline access?" if Google already issued refresh token
.build();
_googleApiClient = new GoogleApiClient.Builder(activity)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
_googleApiClient.connect();
}
#Override
public void onConnected(Bundle connectionHint) {
// Called soon after .connect()
// This is only called when starting our Login process. Sign Out first so select-account screen shown. (OK if not already signed in)
Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
// Start sign in
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(_googleApiClient);
_activity.startActivityForResult(signInIntent, 1); // Activity's onActivityResult will use the same code: 1
}
});
}
#Override
public void onConnectionSuspended(int cause) {
authDone("Connection suspended.");
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) { authDone("Connection failed."); }
public void handleSignInResult(GoogleSignInResult result) {
// Callback from Activity > onActivityResult
if (result.isSuccess()) {
GoogleSignInAccount acct = result.getSignInAccount();
String authCode = acct.getServerAuthCode();
new Thread(new ContinueAuthWithAuthCode_Background(authCode)).start();
}
else authDone("Login canceled or unable to connect to Google."); // can we get better error message?
}
private class ContinueAuthWithAuthCode_Background implements Runnable {
String _authCode;
public ContinueAuthWithAuthCode_Background (String authCode) {
_authCode = authCode;
}
public void run() {
// Convert authCode to tokens
GoogleTokenResponse tokenResponse = null;
String errorMsg = null;
try {
tokenResponse = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), "https://www.googleapis.com/oauth2/v4/token", ClientID, ClientSecret, _authCode, "").execute();
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
final GoogleTokenResponse tokenResponseFinal = tokenResponse;
final String errorMsgFinal = errorMsg;
_handler.post(new Runnable() { public void run() {
// Main thread
GoogleTokenResponse tokenResponse = tokenResponseFinal;
String errorMsg = errorMsgFinal;
if (tokenResponse != null && errorMsg == null) {
_credential.setFromTokenResponse(tokenResponse); // this will keep old refresh token if no new one sent
_refreshToken = _credential.getRefreshToken();
_loggedIn = true;
saveToPrefs();
// FIXME: if our refresh token is bad and we're not getting a new one, how do we deal with this?
Log("New refresh token: " + tokenResponse.getRefreshToken());
}
else if (errorMsg == null) errorMsg = "Get token error."; // shouldn't get here
authDone(errorMsg);
} });
}
}
private void authDone(String errorMsg) {
// Disconnect (we only need googleApiClient for login process)
if (_googleApiClient != null && _googleApiClient.isConnected()) _googleApiClient.disconnect();
_googleApiClient = null;
}
/*
public void signOut() {
Auth.GoogleSignInApi.signOut(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
}
});
}
public void revokeAccess() {
// FIXME: I don't know yet, but this may revoke access for all android devices
Auth.GoogleSignInApi.revokeAccess(_googleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
}
});
}
*/
public void LogOut() {
_loggedIn = false;
saveToPrefs(); // don't clear refresh token as we may need again
}
// -------------------
// API Calls
// -------------------
public void makeApiCall() {
new Thread(new TestApiCall_Background()).start();
}
private class TestApiCall_Background implements Runnable {
public void run() {
FileAndErrorMsg fileAndErr = getFolderFromName_b("Many Files", null);
if (fileAndErr.errorMsg != null) Log("getFolderFromName_b error: " + fileAndErr.errorMsg);
else {
FileListAndErrorMsg fileListAndErr = getFileListInFolder_b(fileAndErr.file);
if (fileListAndErr.errorMsg != null)
Log("getFileListInFolder_b error: " + fileListAndErr.errorMsg);
else {
Log("file count: " + fileListAndErr.fileList.size());
for (File file : fileListAndErr.fileList) {
//Log(file.getName());
}
}
}
_handler.post(new Runnable() { public void run() {
// Main thread
} });
}
}
private FileAndErrorMsg getFolderFromName_b (String folderName, File parent) {
// parent can be null for top level
// Working with folders: https://developers.google.com/drive/v3/web/folder
File folder = null;
folderName = folderName.replace("'", "\\'"); // escape '
String q = String.format(Locale.US, "mimeType='application/vnd.google-apps.folder' and '%s' in parents and name='%s' and trashed=false", parent == null ? "root" : parent.getId(), folderName);
String errorMsg = null;
try {
FileList result = _drive.files().list().setQ(q).setPageSize(1000).execute();
int foundCount = 0;
for (File file : result.getFiles()) {
foundCount++;
folder = file;
}
if (foundCount == 0) errorMsg = "Folder not found: " + folderName;
else if (foundCount > 1) errorMsg = "More than one folder found with name (" + foundCount + "): " + folderName;
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
if (errorMsg != null) folder = null;
return new FileAndErrorMsg(folder, errorMsg);
}
private FileListAndErrorMsg getFileListInFolder_b (File folder) {
// folder can be null for top level; does not return subfolder names
List<File> fileList = new ArrayList<File>();
String q = String.format(Locale.US, "mimeType != 'application/vnd.google-apps.folder' and '%s' in parents and trashed=false", folder == null ? "root" : folder.getId());
String errorMsg = null;
try {
String pageToken = null;
do {
FileList result = _drive.files().list().setQ(q).setPageSize(1000).setPageToken(pageToken).execute();
fileList.addAll(result.getFiles());
pageToken = result.getNextPageToken();
} while (pageToken != null);
}
catch (IOException e) { errorMsg = e.getLocalizedMessage(); }
if (errorMsg != null) fileList = null;
return new FileListAndErrorMsg(fileList, errorMsg);
}
// -------------------
// Misc
// -------------------
private void Log(String msg) {
Log.v("ept", msg);
}
// -------------------
// Load/Save Tokens
// -------------------
private void loadFromPrefs() {
SharedPreferences pref = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
_loggedIn = pref.getBoolean("GoogleLoggedIn", false);
_refreshToken = pref.getString("GoogleRefreshToken", null);
}
private void saveToPrefs() {
SharedPreferences.Editor editor = _context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
editor.putBoolean("GoogleLoggedIn", _loggedIn);
editor.putString("GoogleRefreshToken", _refreshToken);
editor.apply(); // async
}
}
The latest example in https://developers.google.com/drive/v3/web/quickstart/android works out of the box.
Just do the following:
1 - Go to Google API console and create an OAuth2 Client ID using your package name and debug/release key as your Signing-certificate fingerprint.
2 - Enable Google Drive API
3 - Apply the following code
build.gradle : app
compile 'com.google.android.gms:play-services-auth:10.0.1'
compile 'pub.devrel:easypermissions:0.2.1'
compile('com.google.api-client:google-api-client-android:1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
compile('com.google.apis:google-api-services-drive:v3-rev57-1.22.0') {
exclude group: 'org.apache.httpcomponents'
}
Activity
In this code just change the scope to DriveScopes.DRIVE for full drive access
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.*;
import android.Manifest;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
public class MainActivity extends Activity
implements EasyPermissions.PermissionCallbacks {
GoogleAccountCredential mCredential;
private TextView mOutputText;
private Button mCallApiButton;
ProgressDialog mProgress;
static final int REQUEST_ACCOUNT_PICKER = 1000;
static final int REQUEST_AUTHORIZATION = 1001;
static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003;
private static final String BUTTON_TEXT = "Call Drive API";
private static final String PREF_ACCOUNT_NAME = "accountName";
private static final String[] SCOPES = { DriveScopes.DRIVE };
/**
* Create the main activity.
* #param savedInstanceState previously saved instance data.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout activityLayout = new LinearLayout(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
activityLayout.setLayoutParams(lp);
activityLayout.setOrientation(LinearLayout.VERTICAL);
activityLayout.setPadding(16, 16, 16, 16);
ViewGroup.LayoutParams tlp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mCallApiButton = new Button(this);
mCallApiButton.setText(BUTTON_TEXT);
mCallApiButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallApiButton.setEnabled(false);
mOutputText.setText("");
getResultsFromApi();
mCallApiButton.setEnabled(true);
}
});
activityLayout.addView(mCallApiButton);
mOutputText = new TextView(this);
mOutputText.setLayoutParams(tlp);
mOutputText.setPadding(16, 16, 16, 16);
mOutputText.setVerticalScrollBarEnabled(true);
mOutputText.setMovementMethod(new ScrollingMovementMethod());
mOutputText.setText(
"Click the \'" + BUTTON_TEXT +"\' button to test the API.");
activityLayout.addView(mOutputText);
mProgress = new ProgressDialog(this);
mProgress.setMessage("Calling Drive API ...");
setContentView(activityLayout);
// Initialize credentials and service object.
mCredential = GoogleAccountCredential.usingOAuth2(
getApplicationContext(), Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff());
}
/**
* Attempt to call the API, after verifying that all the preconditions are
* satisfied. The preconditions are: Google Play Services installed, an
* account was selected and the device currently has online access. If any
* of the preconditions are not satisfied, the app will prompt the user as
* appropriate.
*/
private void getResultsFromApi() {
if (! isGooglePlayServicesAvailable()) {
acquireGooglePlayServices();
} else if (mCredential.getSelectedAccountName() == null) {
chooseAccount();
} else if (! isDeviceOnline()) {
mOutputText.setText("No network connection available.");
} else {
new MakeRequestTask(mCredential).execute();
}
}
/**
* Attempts to set the account used with the API credentials. If an account
* name was previously saved it will use that one; otherwise an account
* picker dialog will be shown to the user. Note that the setting the
* account to use with the credentials object requires the app to have the
* GET_ACCOUNTS permission, which is requested here if it is not already
* present. The AfterPermissionGranted annotation indicates that this
* function will be rerun automatically whenever the GET_ACCOUNTS permission
* is granted.
*/
#AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
private void chooseAccount() {
if (EasyPermissions.hasPermissions(
this, Manifest.permission.GET_ACCOUNTS)) {
String accountName = getPreferences(Context.MODE_PRIVATE)
.getString(PREF_ACCOUNT_NAME, null);
if (accountName != null) {
mCredential.setSelectedAccountName(accountName);
getResultsFromApi();
} else {
// Start a dialog from which the user can choose an account
startActivityForResult(
mCredential.newChooseAccountIntent(),
REQUEST_ACCOUNT_PICKER);
}
} else {
// Request the GET_ACCOUNTS permission via a user dialog
EasyPermissions.requestPermissions(
this,
"This app needs to access your Google account (via Contacts).",
REQUEST_PERMISSION_GET_ACCOUNTS,
Manifest.permission.GET_ACCOUNTS);
}
}
/**
* Called when an activity launched here (specifically, AccountPicker
* and authorization) exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
* #param requestCode code indicating which activity result is incoming.
* #param resultCode code indicating the result of the incoming
* activity result.
* #param data Intent (containing result data) returned by incoming
* activity result.
*/
#Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case REQUEST_GOOGLE_PLAY_SERVICES:
if (resultCode != RESULT_OK) {
mOutputText.setText(
"This app requires Google Play Services. Please install " +
"Google Play Services on your device and relaunch this app.");
} else {
getResultsFromApi();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == RESULT_OK && data != null &&
data.getExtras() != null) {
String accountName =
data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
SharedPreferences settings =
getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.apply();
mCredential.setSelectedAccountName(accountName);
getResultsFromApi();
}
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode == RESULT_OK) {
getResultsFromApi();
}
break;
}
}
/**
* Respond to requests for permissions at runtime for API 23 and above.
* #param requestCode The request code passed in
* requestPermissions(android.app.Activity, String, int, String[])
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(
requestCode, permissions, grantResults, this);
}
/**
* Callback for when a permission is granted using the EasyPermissions
* library.
* #param requestCode The request code associated with the requested
* permission
* #param list The requested permission list. Never null.
*/
#Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// Do nothing.
}
/**
* Callback for when a permission is denied using the EasyPermissions
* library.
* #param requestCode The request code associated with the requested
* permission
* #param list The requested permission list. Never null.
*/
#Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// Do nothing.
}
/**
* Checks whether the device currently has a network connection.
* #return true if the device has a network connection, false otherwise.
*/
private boolean isDeviceOnline() {
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
/**
* Check that Google Play services APK is installed and up to date.
* #return true if Google Play Services is available and up to
* date on this device; false otherwise.
*/
private boolean isGooglePlayServicesAvailable() {
GoogleApiAvailability apiAvailability =
GoogleApiAvailability.getInstance();
final int connectionStatusCode =
apiAvailability.isGooglePlayServicesAvailable(this);
return connectionStatusCode == ConnectionResult.SUCCESS;
}
/**
* Attempt to resolve a missing, out-of-date, invalid or disabled Google
* Play Services installation via a user dialog, if possible.
*/
private void acquireGooglePlayServices() {
GoogleApiAvailability apiAvailability =
GoogleApiAvailability.getInstance();
final int connectionStatusCode =
apiAvailability.isGooglePlayServicesAvailable(this);
if (apiAvailability.isUserResolvableError(connectionStatusCode)) {
showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
}
}
/**
* Display an error dialog showing that Google Play Services is missing
* or out of date.
* #param connectionStatusCode code describing the presence (or lack of)
* Google Play Services on this device.
*/
void showGooglePlayServicesAvailabilityErrorDialog(
final int connectionStatusCode) {
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
Dialog dialog = apiAvailability.getErrorDialog(
MainActivity.this,
connectionStatusCode,
REQUEST_GOOGLE_PLAY_SERVICES);
dialog.show();
}
/**
* An asynchronous task that handles the Drive API call.
* Placing the API calls in their own task ensures the UI stays responsive.
*/
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private com.google.api.services.drive.Drive mService = null;
private Exception mLastError = null;
MakeRequestTask(GoogleAccountCredential credential) {
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
mService = new com.google.api.services.drive.Drive.Builder(
transport, jsonFactory, credential)
.setApplicationName("Drive API Android Quickstart")
.build();
}
/**
* Background task to call Drive API.
* #param params no parameters needed for this task.
*/
#Override
protected List<String> doInBackground(Void... params) {
try {
return getDataFromApi();
} catch (Exception e) {
mLastError = e;
cancel(true);
return null;
}
}
/**
* Fetch a list of up to 10 file names and IDs.
* #return List of Strings describing files, or an empty list if no files
* found.
* #throws IOException
*/
private List<String> getDataFromApi() throws IOException {
// Get a list of up to 10 files.
List<String> fileInfo = new ArrayList<String>();
FileList result = mService.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files != null) {
for (File file : files) {
fileInfo.add(String.format("%s (%s)\n",
file.getName(), file.getId()));
}
}
return fileInfo;
}
#Override
protected void onPreExecute() {
mOutputText.setText("");
mProgress.show();
}
#Override
protected void onPostExecute(List<String> output) {
mProgress.hide();
if (output == null || output.size() == 0) {
mOutputText.setText("No results returned.");
} else {
output.add(0, "Data retrieved using the Drive API:");
mOutputText.setText(TextUtils.join("\n", output));
}
}
#Override
protected void onCancelled() {
mProgress.hide();
if (mLastError != null) {
if (mLastError instanceof GooglePlayServicesAvailabilityIOException) {
showGooglePlayServicesAvailabilityErrorDialog(
((GooglePlayServicesAvailabilityIOException) mLastError)
.getConnectionStatusCode());
} else if (mLastError instanceof UserRecoverableAuthIOException) {
startActivityForResult(
((UserRecoverableAuthIOException) mLastError).getIntent(),
MainActivity.REQUEST_AUTHORIZATION);
} else {
mOutputText.setText("The following error occurred:\n"
+ mLastError.getMessage());
}
} else {
mOutputText.setText("Request cancelled.");
}
}
}
}
I am new here and reason for that I don't know all rules of the site yet, sorry about that!
I am building a Java application to Desktop and Android by using Gluon mobile plugin in Eclipse tool. I have variated code for Desktop and Android as described in an example (http://docs.gluonhq.com/samples/gonative/). So I have Android specific NativeService where I have a method to read files from external storage (that works). I have also studied how to request permissions on run time when using native Android environment (Storage permission error in Marshmallow ). I am able to request the permission, but I can not catch the result.
I am now requesting the permissions same way, but how I can receive the results? I cannot overrrive onRequestPermissionsResult as in the example, becauce I have not directly available android.support.v4.app stuff. Could someone help me and provide an example?
AndroidNativeService.java:
package com.tentaattorix;
import java.io.IOException;
import java.io.File;
import android.os.Environment;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.content.Context;
import javafxports.android.FXActivity;
import android.util.Log;
import android.widget.Toast;
import android.content.pm.PackageManager;
import android.os.Build;
import com.avustajat.LueJaKirjoita;
/**
* Sanaston luku: Android-natiivi toteutus!
* #author PT
* #version 24.10.2016
*
*/
public class AndroidNativeService implements NativeService {
private static final String TAG = "Tentaattorix Native Service";
private Sanastot sanastot = new Sanastot();
private static final int MY_PERMISSIONS_USE_EXTERNAL_STORAGE = 1;
public AndroidNativeService() {
//
}
#Override
public Sanastot haeSanastot(String juuriKansio, String sanastoRegex, char kommentti, char erotin) throws IOException {
String polku = Environment.getExternalStorageDirectory().toString();
String readme = "LueMinut.txt";
String kansioPolku ="";
//Luodaan kansio, jos sitä ei vielä ole.
File kansio = new File(polku, juuriKansio);
kansio.mkdir();
//Asetetaan oikeudet, jos vaikka auttais skannaukseen.
kansio.setWritable(true);
kansio.setReadable(true);
kansio.setExecutable(true);
kansioPolku = kansio.getAbsolutePath();
//Kysy oikeudet, jos niitä ei ole!
if (isStoragePermissionGranted()) {
//Luodaan kansioon tiedosto LueMinut.txt.
try {
LueJaKirjoita.luoLueMinut(kansioPolku, readme);
}
catch (IOException e){
throw e;
}
//Informoidaan uudesta kansiosta ja sinne tulevista tiedostoista järjestelmää!
scanFile(kansioPolku + File.separator + readme);
//Luetaan sanastot kansiosta.
sanastot = LueJaKirjoita.lueTiedostot(kansioPolku, sanastoRegex, kommentti, erotin);
}
// Jos sanastot ei sisällä yhtään sanastoa,
// niin laitetaan edes yksi :)
if (sanastot.annaLkm() < 1) {
String[] rivix = {"Tyhjä sanasto!", "Empty glossary!"};
Sanasto san = new Sanasto("sanasto_");
san.lisaa(rivix);
sanastot.lisaa(san);
}
return sanastot;
}
/**
* //Informoidaan uudesta kansiosta ja sinne tulevista tiedostoista järjestelmää!
* #param path lisätty polku+tiedosto
*/
private void scanFile(String path) {
MediaScannerConnection.scanFile(FXActivity.getInstance().getApplicationContext(),
new String[] { path }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("TAG", "Finished scanning " + path);
}
});
}
private boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (FXActivity.getInstance().checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
FXActivity.getInstance().requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_USE_EXTERNAL_STORAGE);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
}
For starters, you can add the android-support-v4.jar to your project:
Copy it from its location under ANDROID_HOME/extras/android/support/v4/android-support-v4.jar to a libs folder in your project, and then add the dependency to the build.gradle file:
dependencies {
androidCompile files('libs/android-support-v4.jar')
}
Assuming you are targeting Android SDK 23+:
android {
minSdkVersion '23'
compileSdkVersion '23'
targetSdkVersion '23'
manifest = 'src/android/AndroidManifest.xml'
}
then you know that by default all the permissions included in the manifest will be disabled.
If you want to check for permissions on runtime, you can define a new activity that takes care of requesting permissions with a built-in dialog (using ActivityCompat.requestPermissions), register this activity in the manifest, and call it from the FXActivity within a new intent that passes a list with the required permissions.
You just need to call FXActivity.getInstance().setOnActivityResultHandler() to listen to the end of that activity and resume the call if permissions were granted.
The following code is partly based in the PermissionHelper class.
I'll use the simple case of the Dialer service from the new Charm Down 3.0.0 library, that requires Manifest.permission.CALL_PHONE.
AndroidDialerService.java, under Android package
public class AndroidDialerAskService implements DialerAskService {
public static final String KEY_PERMISSIONS = "permissions";
public static final String KEY_GRANT_RESULTS = "grantResults";
public static final String KEY_REQUEST_CODE = "requestCode";
#Override
public void call(String number) {
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(FXActivity.getInstance(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
FXActivity.getInstance().setOnActivityResultHandler((requestCode, resultCode, data) -> {
if (requestCode == 11112) {
// if now we have permission, resume call
if (ContextCompat.checkSelfPermission(FXActivity.getInstance(), Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
call(number);
}
}
});
Intent permIntent = new Intent(FXActivity.getInstance(), PermissionRequestActivity.class);
permIntent.putExtra(KEY_PERMISSIONS, new String[]{Manifest.permission.CALL_PHONE});
permIntent.putExtra(KEY_REQUEST_CODE, 11111);
FXActivity.getInstance().startActivityForResult(permIntent, 11112);
return;
}
}
if (number != null && !number.isEmpty()) {
Uri uriNumber = Uri.parse("tel:" + number);
Intent dial = new Intent(Intent.ACTION_CALL, uriNumber);
FXActivity.getInstance().startActivity(dial);
}
}
public static class PermissionRequestActivity extends Activity {
private String[] permissions;
private int requestCode;
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
FXActivity.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults);
finish();
}
#Override
protected void onStart() {
super.onStart();
permissions = this.getIntent().getStringArrayExtra(KEY_PERMISSIONS);
requestCode = this.getIntent().getIntExtra(KEY_REQUEST_CODE, 0);
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
}
}
AndroidManifest.xml
. . .
<uses-permission android:name="android.permission.CALL_PHONE"/>
. . .
<activity android:name="javafxports.android.FXActivity" .../>
<activity android:name="com.gluonhq.charm.down.plugins.android.AndroidDialerService$PermissionRequestActivity" />
. . .
I am trying to run Android Google Drive. I am using the github sample code Android Demos.
I have added google play services to my project. This is the error Iam getting:
The import com.google.android.gms.drive.events.DriveEvent.ChangeListener cannot be resolved
After reading this page (Error com.google.android.gms.drive.DriveApi.DriveContentsResult cannot be resolved)
I changed it to
The import com.google.android.gms.drive.events.ChangeListener
But, the error still exists within those two methods:
Listener cannot be resolved to a type
-
private void toggle() {
if (mSelectedFileId == null) {
return;
}
synchronized (mSubscriptionStatusLock) {
DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(),
mSelectedFileId);
if (!isSubscribed) {
Log.d(TAG, "Starting to listen to the file changes.");
file.addChangeListener(getGoogleApiClient(), changeListener);//error
isSubscribed = true;
} else {
Log.d(TAG, "Stopping to listen to the file changes.");
file.removeChangeListener(getGoogleApiClient(), changeListener);//error
isSubscribed = false;
}
}
refresh();
}
-
final private Listener<ChangeEvent> changeListener = new Listener<ChangeEvent>() {//error
#Override
public void onEvent(ChangeEvent event) {
mLogTextView.setText(String.format("File change event: %s", event));
}
};
Can anybody enlighten me on this issue?
Github code
https://github.com/googledrive/android-demos
I'm using this code:
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.drive.DriveApi;
import com.google.android.gms.drive.DriveResource;
import com.google.android.gms.drive.events.ChangeEvent;
import com.google.android.gms.drive.events.ChangeListener;
PendingResult<DriveApi.DriveIdResult> pendingResult = Drive.DriveApi.fetchDriveId(mGoogleApiClient, id);
pendingResult.setResultCallback(new ResultCallback<DriveApi.DriveIdResult>() {
#Override
public void onResult(#NonNull DriveApi.DriveIdResult driveIdResult) {
if (!driveIdResult.getStatus().isSuccess()) {
Log.d(TAG, String.format("fetch drive id error: %s", driveIdResult.getStatus().getStatusMessage()));
return;
}
mCurrentDriveId = driveIdResult.getDriveId();
DriveResource resource = mCurrentDriveId.asDriveResource();
Log.d(TAG, "received driveid from id");
PendingResult<Status> pendingChange = resource.addChangeListener(mGoogleApiClient, new ChangeListener() {
#Override
public void onChange(ChangeEvent changeEvent) {
onResourceChange(changeEvent);
}
});
pendingChange.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.d(TAG, "change listener success");
} else {
Log.d(TAG, String.format("change listener error: %s", status.getStatusMessage()));
}
}
});
}
});
But it looks like the changes are not too much realtime.
Anyway getFile() is obsolete.
I am trying to add content observer for AOSP Browser's history provider i.e., with Uri Browser.BOOKMARKS_URI. If I attach an observer, the onChange(boolean) gets called on my ICS running my Samsung GT-S7562, my JB running Samsung GT-I8262 or HTC Desire X but I don't get any notifications for AOSP Browser provider on my friend's Android 4.3 running Samsung SM-G7102. However for Google Chrome's provider, i.e., residing at content://com.android.chrome.browser/bookmarks, I get notified for changes on all android releases. Also if I query the AOSP Browser database & scan for entries, it only returns those opened in Chrome (I mean it returns Chrome's history in Browser.BOOKMARKS_URI provider).
Below is the sample service I tested for reference which is not actually required (don't mind but I think everything is quite clear above already):
package vpz.hp;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class Watcher extends Service {
private Provider Browser;
private Provider Chrome;
#Override public IBinder onBind(Intent intent) {
return null;
}
#Override public void onDestroy() {
if (this.Browser != null)
this.Browser.Stop();
if (this.Chrome != null)
this.Chrome.Stop();
super.onDestroy();
}
public final int onStartCommand(Intent Intention, int StartId, int Flags) {
this.Browser = new Provider(super.getApplicationContext(), android.provider.Browser.BOOKMARKS_URI);
this.Browser.Start();
if (Search(getApplicationContext(), "com.android.chrome")) {
this.Chrome = new Provider(super.getApplicationContext(), Uri.parse("content://com.android.chrome.browser/bookmarks"));
this.Chrome.Start();
}
return START_STICKY;
}
private static final boolean Search(Context Sender, String Package) {
try {
return Sender.getPackageManager().getPackageInfo(Package, PackageManager.GET_ACTIVITIES) != null;
} catch (NameNotFoundException Error) {
return false;
}
}
private static class Provider extends ContentObserver {
private Context Sender;
private Uri URI;
public Provider(Context Sender, Uri URI) {
super(new Handler());
this.Sender = Sender;
this.URI = URI;
}
#Override public void onChange(boolean Self) {
String Message = this.URI.toString() + " onChange(" + Self + ")";
Toast.makeText(this.Sender, Message, Toast.LENGTH_SHORT).show();
Log.e("VPZ", Message);
}
public final void Start() {
this.Sender.getContentResolver().registerContentObserver(this.URI, true, this);
}
public final void Stop() {
this.Sender.getContentResolver().unregisterContentObserver(this);
}
}
}
I have added below permission(s) already in the manifest:
<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
Is content observer on AOSP Browser has been deprecated?
These are the minimum details i need to login to my application:
guid(Global Unique ID)
fname
lname
email
gender
for Facebook login it was clear and could do this:
JSONObject json = Util.parseJson(facebook.request("me");
to get all the data specified above.
Is, there a way i can do something similar and simple with Gmail?
I read this which says:
No, there is no such SDK(like facebook) and if you want to access the
emails of Gmail then you need to implement your own email client and
for that follow the above link shared by Spk.
But i only want few user details, I don't need anything related to his mails etc.., which come under Authorization (if I am right). I only need Authentication:
I also read this, but doesn't look like it will help any.
This is the code i have now:
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.plus.GooglePlusUtil;
import com.google.android.gms.plus.PlusClient;
import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity implements ConnectionCallbacks,
OnConnectionFailedListener {
private static final int REQUEST_CODE_RESOLVE_ERR = 7;
private ProgressDialog mConnectionProgressDialog;
private PlusClient mPlusClient;
private ConnectionResult mConnectionResult;
private String TAG = "GmailLogin";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
int errorCode = GooglePlusUtil.checkGooglePlusApp(this);
if (errorCode != GooglePlusUtil.SUCCESS) {
GooglePlusUtil.getErrorDialog(errorCode, this, 0).show();
} else {
mPlusClient = new PlusClient.Builder(this, this, this)
.setVisibleActivities( "http://schemas.google.com/AddActivity",
"http://schemas.google.com/BuyActivity").build();
mConnectionProgressDialog = new ProgressDialog(this);
mConnectionProgressDialog.setMessage("Signing in...");
Button signInButton = (Button) findViewById(R.id.sign_in_button);
signInButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mConnectionResult == null) {
mConnectionProgressDialog.show();
} else {
try {
mConnectionResult
.startResolutionForResult(
MainActivity.this,
REQUEST_CODE_RESOLVE_ERR);
} catch (SendIntentException e) {
// Try connecting again.
mConnectionResult = null;
mPlusClient.connect();
}
}
}
});
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public void onConnectionFailed(ConnectionResult result) {
if (result.hasResolution()) {
try {
result.startResolutionForResult(this, REQUEST_CODE_RESOLVE_ERR);
} catch (SendIntentException e) {
mPlusClient.connect();
}
}
// Save the result and resolve the connection failure upon a user click.
mConnectionResult = result;
}
#Override
protected void onActivityResult(int requestCode, int responseCode,
Intent intent) {
if (requestCode == REQUEST_CODE_RESOLVE_ERR
&& responseCode == RESULT_OK) {
mConnectionResult = null;
mPlusClient.connect();
}
}
#Override
public void onConnected() {
String accountName = mPlusClient.getAccountName();
Toast.makeText(this, accountName + " is connected.", Toast.LENGTH_LONG)
.show();
}
#Override
public void onDisconnected() {
Log.d(TAG, "disconnected");
}
#Override
protected void onStart() {
super.onStart();
mPlusClient.connect();
}
#Override
protected void onStop() {
super.onStop();
mPlusClient.disconnect();
}
}
Any help is greatly appreciated, Thank You.
You should not use Gmail for user authentication using Google accounts. You can use Google + Sign-in for Android instead. This will allow you to access the user's profile information upon getting the required permissions using OAuth. Check out the guide here:
https://developers.google.com/+/mobile/android/sign-in
Those attributes would be passed as claims in the original authentication, as well as on each subsequent authentication.
Google allows you to request that they include up to Country, Email, First Name, Language, and Last Name as claims on the authentication token.
Since they do not provide a uuid, you will have to create one off of the email address. There is no support (that I could find) for retrieving gender.