My application created files with a custom Mime type and stores them on Google Drive. The app can search and reopen these files just fine too. However, when I click the file in the Google Drive app (not my own app) the open flow does not work.
The Chooser Intent shows as expected with just my application listed, but when I select my application the Google Drive app briefly shows a downloading progress bar that never starts and then says there is an internal error.
My setup is below, I'm hoping somebody can tell me what causes this. although I would assume nothing is actually contacting my app by this point. The developer console has been filled in correctly as far as I know.
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="...">
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="#drawable/logo" android:label="#string/app_name"
android:allowBackup="true" android:theme="#style/AppTheme">
<meta-data android:name="com.google.android.gms.version" android:value="#integer/google_play_services_version" />
<activity android:name=".MainActivity" android:label="#string/app_name"
android:launchMode="singleTop" android:theme="#style/AppTheme">
<meta-data android:name="com.google.android.apps.drive.APP_ID" android:value="id=..." />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.apps.drive.DRIVE_OPEN" />
<data android:mimeType="#string/app_mime" />
<data android:mimeType="#string/file_mime" /> <!-- matches the file im clicking -->
</intent-filter>
</activity>
</application>
Activity
public class MainActivity extends Activity {
private static final String ACTION_DRIVE_OPEN = "com.google.android.apps.drive.DRIVE_OPEN";
private static final String EXTRA_DRIVE_OPEN_ID = "resourceId";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
#Override
protected void onResume() {
super.onResume();
handleIntent();
}
private void handleIntent() {
Intent intent = getIntent();
if (ACTION_DRIVE_OPEN.equals(intent.getAction())) {
if (intent.getType().equals(getString(R.string.file_mime))) {
String fileId = intent.getStringExtra(EXTRA_DRIVE_OPEN_ID);
if (fileId != null && !"".equals(fileId)) {
...
} else {
Log.e(TAG, "Drive_Open has no valid file id - " + fileId);
}
} else {
Log.e(TAG, "Drive_Open called on the wrong mime type - " + intent.getType() + " found, " + getString(R.string.file_mime) + " required");
}
}
}
After some testing this seems to happen after there is a timeout between the drive app connecting to the server to retrieve information and passing it to the other app (or any other fault).
The initial problem will give you a meaningful error but then the drive app seems to maintain a cache or something which cases the above error to be displayed on all future attempts.
The solution is to either clear the data of both apps (which can be problematic) or (I found easier) re-install the app you are developing/testing. Either method stops this problem reoccurring.
Related
Preconditions
1. App starts with LinkActivity, at this point we have no deep link intent, it's ok.
Main activity launched. There we are able to click the deep link.
By clicking on deep link opens LinkActivity, uri is correct, referringParams json is not empty (ok). But...
When we replaying step 2: uri is correct, but the reffering params are empty: "{}"; All other tries are with the same result.
Only when we pausing the app (for example switching to the recent apps menu) and then returning to the app - deep link works as expected, but only at first try. May be some issues with the session close (but in the current version of the sdk it self controls session close)
public class LinkActivity extends AppCompatActivity {
private static final String TAG = LinkActivity.class.getSimpleName();
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onStart() {
super.onStart();
Uri uri = getIntent().getData();
Log.w(TAG, "uri: " + uri);
Branch.getInstance().initSession(new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
Log.w(TAG, "json: " + referringParams);
startActivity(new Intent(LinkActivity.this, MainActivity.class));
}
}, uri, this);
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
public class BranchApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Branch.enableLogging();
Branch.getAutoInstance(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.myapp">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".BranchApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".LinkActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="myapp.link"
android:scheme="https" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"/>
<meta-data
android:name="io.branch.sdk.BranchKey"
android:value="#string/branch_io_live_key" />
<meta-data
android:name="io.branch.sdk.BranchKey.test"
android:value="#string/branch_io_test_key" />
<meta-data
android:name="io.branch.sdk.TestMode"
android:value="false" />
</application>
</manifest>
implementation "io.branch.sdk.android:library:2.14.3"
Update:
Even with android:launchMode="singleInstance" for LinkActivity steel reproduces (I don't think this is the case).
Udpate2:
Bhardwaj mentioned that no need to call initSession when we initing Branch via getAutoInstance. But how to get refferingParams from uri in that case?
Update3:
From the Branch.checkIntentForSessionRestart doc:
Check for forced session restart. The Branch session is restarted if
the incoming intent has branch_force_new_session set to true. This is
for supporting opening a deep link path while app is already running
in the foreground. Such as clicking push notification while app in
foreground.
So, My desired behavior is matches this description. But how to force session restart?
You can try as mentioned below :-
Branch.getAutoInstance(this) -> Branch.getAutoInstance(this, true)
Branch.getInstance(context) -> Branch.getInstance()
Do not call initSession when you have getAutoInstance()
if(!initiatedBranchDeepLinks) {
// Configure Branch.io
initiatedBranchDeepLinks = true;
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
String message = "Branch.io onInitFinished. Params: " + referringParams.toString();
Log.d(TAG, message);
} else {
Log.i(TAG, error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
Here is Branch Test Bed app:
https://github.com/BranchMetrics/android-branch-deep-linking/tree/master/Branch-SDK-TestBed
You can use this as a reference and see what you are doing incorrectly.
This could be caused by your Manifest configuration. In your <activity> tag, you should include android:launchMode="singleTask". See this section of our docs. This may explain why you are receiving the parameters the first time, but not receiving them on a re-open.
I am working on app which suggested app. If user installs app successfully using my app then he gets reward in my app.
I am getting info about "com.android.vending.INSTALL_REFERRER" action of receiver which provides this but didn't get success...
So please help me with getting some full examples or other suggestion...
This is my code...
Button Click Event
btnInstallApp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent goToMarket = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("market://details?id=com.idea.backup.smscontacts&referrer=tecksky"));
startActivity(goToMarket);
}
});
Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tecksky.referrerdemo">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".receiver.ReferrerCatcher"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER"></action>
</intent-filter>
</receiver>
</application>
</manifest>
ReferrerCatcher.java
public class ReferrerCatcher extends BroadcastReceiver {
private static String referrer = "";
#Override
public void onReceive(Context context, Intent intent) {
referrer = "";
Bundle extras = intent.getExtras();
if (extras != null) {
referrer = extras.getString("referrer");
}
Log.e("REFERRER : ", referrer);
}
}
Hey Just see how it works I have implemented this in my app as well and works for me perfectly so I am posting you referral code:
Create separate class for receiving reference:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.gms.analytics.CampaignTrackingReceiver;
public class CustomReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
new CampaignTrackingReceiver().onReceive(context, intent);
Bundle b= intent.getExtras();
String referrerString = b.getString("referrer");
// Log.e("bundle", "bundle= " + referrerString+ " " + referrerString.substring(11, referrerString.length()));
SharedPrefManager.setPrefVal(context, Constants.REFERRAL, referrerString.substring(11, referrerString.length()));
}
}
This class will receive the referral that you have to save somewhere like I am saving it in SharedPrefrence. This broadcast will receive when the user will install app from play store and in "referrer" you will get info that which user pass this user the link to download app then from that referrer key you can give benefit to user who referred your app.
In manifest file add receiver Tag
<receiver
android:name="gcm.CustomReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
From backend you will get a link from server side for a particular user which you will hit to play store then something like
https://play.google.com/store/apps/details?id=com.example.app&referrer=utm_source=123456ref
Basically in your receiver class you are picking this utm_source and you have to send it to server side when user will sign up in you app. Which means it ll let know that from which reference which id has been generated.
Hope this helps you. Try this and let me know.
I am learning Android and NFC programming through the official android developer tutorial. What I want is to write an app that will be triggered by NFC tag. When the app starts, I want it to display a toast message containing the UID of the scanned tag. My simple code to achieve this is:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
Toast.makeText(getApplicationContext(), "UID: " + bin2hex(tag.getId()), Toast.LENGTH_SHORT).show();
}
// Parsing binary to string
static String bin2hex(byte[] data) {
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1,data));
}
I also update the manifest file to enable NFC as shown below:
<uses-permission android:name="android.permission.NFC"/>
....
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
<activity
android:name="com.testapp.testnfc.MainActivity"
android:label="#string/app_name" >
.....
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
Then I use "NFC Task Launcher" app from Play Store to write into my tag so that the tag will trigger my new app. After creating the tag, tapping on it will successfully start my new app, but the app fails to display the toast of the UID and it will crash stating "Unfortunately Test NFC has stopped." What did I miss to cause this crash?
Try it like:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
Toast.makeText(this,bin2hex(tag.getId()),Toast.LENGTH_LONG).show();
}
}
I am facing issue in opening Email in MYApp when I made its launch mode to "singleInstance".
I have attached sample Android project which reads file name from email attachment and displays it on screen.
Works fine in case of onCreate but throws error in onNewIntent when apps launch mode is singleInstance.
Launchmode.java
package your.namespace.launchmode;
public class LaunchModeActivity extends Activity {
private static final int OPEN_ACT = 2;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String name = getAttachmetName(getIntent());
if(null != name)
{
TextView textv = (TextView) findViewById(R.id.attachmentnm);
textv.setText(name);
}
}
#Override
protected void onNewIntent(Intent savedInstanceState)
{
super.onNewIntent(savedInstanceState);
String name = getAttachmetName(savedInstanceState);
if(null != name)
{
TextView textv = (TextView) findViewById(R.id.attachmentnm);
textv.setText(name);
}
}
private String getAttachmetName(Intent intent) {
final Uri documentUri = intent.getData();
if(null != documentUri){
final String uriString = documentUri.toString();
String documentFilename = null;
final int mailIndexPos = uriString.lastIndexOf("/attachments");
if (mailIndexPos != -1) {
final Uri curi = documentUri;
final String [] projection = new String[] {OpenableColumns.DISPLAY_NAME};
final Cursor cursor = getApplicationContext().getContentResolver().query(curi, projection, null, null, null);
if (cursor != null) {
final int attIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (attIdx != -1) {
cursor.moveToFirst();
documentFilename = cursor.getString(attIdx);
}
cursor.close();
}
}
return documentFilename;
}
return null;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
if((resultCode == RESULT_OK) && (requestCode == OPEN_ACT))
{
Log.d("LaunchMode", "Second activity returned");
}
}
}
AndroidManifest
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.namespace.launchmode"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="com.google.android.gm.permission.READ_GMAIL"/>
<uses-permission android:name="com.google.android.gm.permission.WRITE_GMAIL"/>
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL"/>
<uses-permission android:name="com.google.android.providers.gmail.permission.WRITE_GMAIL"/>
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:label="#string/app_name"
android:launchMode="singleInstance"
android:name=".LaunchModeActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- docx -->
<data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
<!-- xlsx -->
<data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
<!-- pptx -->
<data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
<data android:mimeType="application/vnd.ms-excel" />
<data android:mimeType="application/msword" />
<data android:mimeType="application/vnd.ms-powerpoint" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
</manifest>
Steps to reproduce
1)Install apk on device.
2)Go to gmail native app on device, open any attachment(office document) to view.
3)Choose LaunchMode app to complete action.
4)LaunchMode app will display file name on screen.
This works fine for first time (onCreate flow) but when this app is switch in background and again I try 2,3,4 steps.. app crashes with error
E/DatabaseUtils(30615): java.lang.SecurityException: Permission Denial: reading com.google.android.gm.provider.MailProvider uri content://gmail-ls/qoconnect#gmail.com/messages/5/attachments/0.2/BEST/false from pid=32657, uid=10058 requires com.google.android.gm.permission.READ_GMAIL
E/DatabaseUtils(30615): at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:309)
E/DatabaseUtils(30615): at android.content.ContentProvider$Transport.bulkQuery(ContentProvider.java:178)
E/DatabaseUtils(30615): at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:111)
E/DatabaseUtils(30615): at android.os.Binder.execTransact(Binder.java:339)
E/DatabaseUtils(30615): at dalvik.system.NativeStart.run(Native Method)
D/AndroidRuntime(32657): Shutting down VM
I need to fix this as, I need to have single instance of Application and should get email attachment name too.
Please let me know If I am missing something here.
My question here is why it work in flow of onCreate and it wont work in case of onNewIntent
Note:
1)Works fine with 2.x phones
2) Works fine with Single top launch mode.
3) Some updates on Gmail app.link here:
You likely got a URI permission to read the file name when you received the intent and aren't using the permissions that you requested (READ_GMAIL and WRITE_GMAIL). The URI permission is valid only until your application finish()es, so you won't have it when you try to resume.
That's consistent with your experiences - it works when the intent is fresh, but not old ones. I think that WRITE_GMAIL is a signature permission and I am guessing that READ_GMAIL is as well. In that case, there is not much you can do. READ_ATTACHMENT might be a more appropriate permission for you to request.
More on URI permissions: http://developer.android.com/guide/topics/security/permissions.html#uri
Try removing the uses-permission tags from your manifest and see if you have the same experience. You can also try to examine the intent when you receive it by checking its flags.
checkCallingOrSelfUriPermission(documentUri , Intent.FLAG_GRANT_READ_URI_PERMISSION)
If you get 0 returned, you have been using URI permissions.
Like skoke said, you can no longer read from GMail if the intent that gave your permission is not fresh, i.e., it must be the original activity intent. If your intent is from onNewIntent then it probably won't succeed.
My solution isn't beautiful but seems to be working. In my onResume I called a custom function to see if I could access the Gmail content. If not, I showed the user a message and asked them to close the app and try again. Hey, at least it doesn't crash.
My Android application uses Java OAuth library, found here for authorization on Twitter. I am able to get a request token, authorize the token and get an acknowlegement but when the browser tries the call back url to reconnect with my application, it does not use the URL I provide in code, but uses the one I supplied while registering with Twitter.
Note:
1. When registering my application with twitter, I provided a hypothetical call back url:http://abz.xyc.com and set the application type as browser.
2. I provided a callback url in my code "myapp" and have added an intent filter for my activity with Browsable category and data scheme as "myapp".
3. URL called when authorizing does contain te callback url, I specified in code.
Any idea what I am doing wrong here?
Relevant Code:
public class FirstActivity extends Activity
{
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
OAuthAccessor client = defaultClient();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(client.consumer.serviceProvider.userAuthorizationURL + "?oauth_token="
+ client.requestToken + "&oauth_callback=" + client.consumer.callbackURL));
startActivity(i);
}
OAuthServiceProvider defaultProvider()
{
return new OAuthServiceProvider(GeneralRuntimeConstants.request_token_URL,
GeneralRuntimeConstants.authorize_url, GeneralRuntimeConstants.access_token_url);
}
OAuthAccessor defaultClient()
{
String callbackUrl = "myapp:///";
OAuthServiceProvider provider = defaultProvider();
OAuthConsumer consumer = new OAuthConsumer(callbackUrl,
GeneralRuntimeConstants.consumer_key, GeneralRuntimeConstants.consumer_secret,
provider);
OAuthAccessor accessor = new OAuthAccessor(consumer);
OAuthClient client = new OAuthClient(new HttpClient4());
try
{
client.getRequestToken(accessor);
} catch (Exception e)
{
e.printStackTrace();
}
return accessor;
}
#Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
Uri uri = this.getIntent().getData();
if (uri != null)
{
String access_token = uri.getQueryParameter("oauth_token");
}
}
}
// Manifest file
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".FirstActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp"/>
</intent-filter>
</activity>
</application>
Twitter does not honor callbacks requested in OAuth requests (Twitter API Announce) and will only redirect to the callback URL specified in the Application Settings (note that "localhost" is not allowed).
I assume you checked Oauth-callback-on-android question.
Android guesswork--
After a bit of reading up, I see Android browser redirects MyApp:/// to your application and I'm guessing Twitter doesn't like this bespoke URI prefix. I'm no android developer but one suggestion I might make is to get "www.myapp.com" on the web and have a re-redirect there.
So have your OAuth return to http://www.myapp.com/redirect.aspx?oauth_token=abc and have that page redirect to myapp:///oauth_token=... (the desired result)
In my case, i have this working:
String authURL = m_provider.retrieveRequestToken (m_consumer, CALLBACK_URL);
And in the Manifest:
<activity android:configChanges = "keyboardHidden|orientation" android:name = "xxxx.android.xxxxx">
<intent-filter>
<action android:name = "android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="tweet" />
</intent-filter>
In this case the callback url will be: myapp://tweet
It looks to me like you're doing the correct thing and Twitter is screwing up by always accepting your registered callback URL. Is there any way to change the registered URL? Maybe you could re-register and try an Android callback next time, see what happens.
My problem was that I was trying to log in with the same account I made the Twitter app. After I logged in with my personal profile the call back works (so far).