Android / Google Plus - Cannot share image from my content provider
Asked Answered
C

1

7

I have used this code and can successfully share an image (from my phone's gallery) with text to google+ from my Android app.

However, when I try to post an image from my app's content provider, the image is showing up on my google+ page like this...

enter image description here

...and that is despite the intended image being displayed fine on the google+ app preview screen.

The code I am using to share is:

String message = "My message"; 
Uri localImageUri = ContentUris.withAppendedId(DbContentProvider.CONTENT_URI_PRODUCTS, mProductId;
PlusShare.Builder builder = new PlusShare.Builder(getActivity());
builder.setText(message);
builder.addStream(localImageUri);
builder.setType("image/jpeg");
Intent shareIntent = builder.getIntent();
startActivityForResult(shareIntent, RC_GOOGLE_PLUS);

...and, like I say, the image is successfully displayed on the final google+ page if the localImageUri value is for a resource in my phone's Gallery - whereas the above placeholder image is shown if I set localImageUri to a uri from my app's own content provider.

So I presume there must be an issue with my content provider, which is defined in my manifest as:

<provider
    android:name=".DbContentProvider"
    android:authorities="com.example.provider"
    android:exported="true"
    android:grantUriPermissions="true" />

So, could there be something missing from my manifefst - or even from my searchable.xml file:

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint"

    android:searchSuggestAuthority="com.example.provider"
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestIntentData="content://com.example.provider/suggest"
    android:searchSuggestThreshold="3"

    android:includeInGlobalSearch="true"
    android:searchSettingsDescription="@string/search_settings_description"
    android:queryAfterZeroResults="true"
    android:voiceSearchMode="showVoiceSearchButton" >   
</searchable>

If not, then what could the problem be?

Update (Following CommonsWare's comment)

Exceptions were indeed thrown by the Google+ library because as well doing a query on the _data column (which existed), the Google+ API was also looking for columns that didn't exist - namely, datetaken, date_added, date_modified.

So I have added these columns to my database table - adding them all as text columns with recent millis values such as '1419379390000' (ref here) but I still get the same placeholder image being displayed on my Google+ page.

So I added some logging code to the query() method of my DbContentProvider class and when google+ does its single-column query for datetaken, the value returned in the cursor is indeed 1419379390000. However, the value returned for the (separate) _data query is null.

I'm not sure why the google API queries the _data column (because I do not need to call it in my code when I retrieve the image from the database in order to show in on the UI - instead, I call...

Uri localImageUri = ContentUris.withAppendedId(DbContentProvider.CONTENT_URI_PRODUCTS, mProductId);
InputStream in = cr.openInputStream(localImageUri);
Bitmap img = BitmapFactory.decodeStream(in);
imageView.setImageBitmap(img);

...but presumably the null in my cursor for the _data value is the problem. Not sure where to start to address this, though??

30-Dec-2014 Update

Here is the LogCat output. So, when I click my g+ button...

12-30 21:45:34.344: D/DbContentProvider(24633): getType(content://com.example.provider/products/1668)
12-30 21:45:34.534: D/DbContentProvider(24633): getType(content://com.example.provider/products/1668)
12-30 21:45:34.544: D/DbContentProvider(24633): openFile(content://com.example.provider/products/1668, r)
12-30 21:45:34.584: D/DbContentProvider(24633): openFile(content://com.example.provider/products/1668, r)
12-30 21:45:34.604: D/DbContentProvider(24633): openFile(content://com.example.provider/products/1668, r)
12-30 21:45:34.604: D/DbContentProvider(24633): openFile(content://com.example.provider/products/1668, r)
12-30 21:45:34.624: D/DbContentProvider(24633): openFile(content://com.example.provider/products/1668, r)

...that brings up the g+ screen for customising the share message (which, I confirm, does include the image from my content provider). So I then click the Share button on the g+ screen and the LogCat output is...

12-30 21:45:57.526: D/DbContentProvider(24633): openFile(content://com.example.provider/products/1668, r)
12-30 21:45:57.576: D/DbContentProvider(24633): getType(content://com.example.provider/products/1668)
12-30 21:45:57.576: D/DbContentProvider(24633): DbContentProvider -  query(content://com.example.provider/products/1668)
12-30 21:45:57.576: D/DbContentProvider(24633):  -      projection: {"datetaken"}
12-30 21:45:57.576: D/DbContentProvider(24633):  -      selection: null
12-30 21:45:57.576: D/DbContentProvider(24633):  -      selectionArgs: null
12-30 21:45:57.576: D/DbContentProvider(24633):  -      sortOrder: null
12-30 21:45:57.576: D/DbContentProvider(24633): SQL (without selectionArgs): SELECT datetaken FROM products WHERE (_id = 1668) LIMIT 1
12-30 21:45:57.596: D/DbContentProvider(24633): returned value: 1419379390000
12-30 21:45:57.596: D/DbContentProvider(24633): cursor count: 1
12-30 21:46:03.642: D/DbContentProvider(24633): getType("content://com.example.provider/products/1668") - returns "vnd.android.cursor.item/vnd.example.elemental"

23-Jan-2015 Update

Here is my manifest in full:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example"
    android:versionCode="10"
    android:versionName="0.10" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="20" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

    <permission
        android:name="com.example.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.example.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.android.vending.BILLING" />

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <receiver
            android:name=".GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />

                <category android:name="com.example" />
            </intent-filter>
        </receiver>

        <service android:name=".GcmIntentService" />

        <provider
            android:name=".DbContentProvider"
            android:authorities="com.example.provider"
            android:exported="false"
            android:grantUriPermissions="false" >
            <grant-uri-permission android:pathPrefix="/products" />
        </provider>

        <activity
            android:name="com.google.android.gms.ads.AdActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
            android:theme="@android:style/Theme.Translucent" >
            <meta-data
                android:name="com.google.android.gms.version"
                android:value="@integer/google_play_services_version" />
        </activity>
        <activity
            android:name=".SplashActivity"
            android:label="@string/app_name"
            android:launchMode="singleTop" >
            <intent-filter android:label="@string/app_name_short">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".LoginActivity"
            android:label="@string/title_activity_login"
            android:windowSoftInputMode="stateHidden" >
        </activity>
        <activity
            android:name=".HomeActivity"
            android:label="@string/app_name_short"
            android:windowSoftInputMode="adjustPan" >
            <meta-data
                android:name="android.app.default_searchable"
                android:value=".SearchResultsActivity" />
        </activity>
        <activity
            android:name=".SearchResultsActivity"
            android:label="@string/app_name_short"
            android:launchMode="singleTop"
            android:parentActivityName=".HomeActivity"
            android:windowSoftInputMode="stateHidden" >
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>

            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".HomeActivity" />
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
            <meta-data
                android:name="android.app.default_searchable"
                android:value=".SearchResultsActivity" />
        </activity>
        <activity
            android:name=".SingleShoppingListActivity"
            android:label="@string/shopping_list"
            android:parentActivityName=".HomeActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".HomeActivity" />
        </activity>
        <activity
            android:name=".SingleProductActivity"
            android:label="@string/shopping_list_item"
            android:parentActivityName=".HomeActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".HomeActivity" />
        </activity>
        <activity
            android:name=".InfoMenuActivity"
            android:label="@string/why_gmo_free"
            android:parentActivityName=".HomeActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".HomeActivity" />
        </activity>
        <activity
            android:name=".InfoContentActivity"
            android:label="@string/why_gmo_free"
            android:parentActivityName=".InfoMenuActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".InfoMenuActivity" />
        </activity>
        <activity
            android:name=".SettingsActivity"
            android:label="@string/settings"
            android:parentActivityName=".HomeActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".HomeActivity" />
        </activity>
        <activity
            android:name=".TwitterCallbackActivity"
            android:label="@string/app_name" >
            <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:host="twitter"
                    android:scheme="oauth" />
            </intent-filter>
        </activity>
        <activity
            android:name=".DummyActivity"
            android:label="@string/title_activity_dummy" >
        </activity>
        <activity
            android:name=".SmartSearchResultsActivity"
            android:label="@string/app_name_short"
            android:parentActivityName=".SearchResultsActivity"
            android:windowSoftInputMode="stateHidden" >
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>

            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".SearchResultsActivity" />
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
            <meta-data
                android:name="android.app.default_searchable"
                android:value=".SearchResultsActivity" />
        </activity>
        <activity
            android:name=".WebViewActivity"
            android:label="@string/app_name_short"
            android:parentActivityName=".HomeActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".HomeActivity" />
        </activity>
        <activity
            android:name=".UpgradeActivity"
            android:label="@string/upgrade_to_pro_qm" >
        </activity>
    </application>

</manifest>
Croon answered 21/12, 2014 at 16:42 Comment(12)
Anything interesting show up in LogCat during the sharing process? It definitely seems bizarre that G+ can access the image (preview works) but then hiccups when accessing the image (post fails). I would expect either both actions to succeed or fail. BTW, is DbContentProvider a FileProvider implementation, or one you created? If the latter, how you are getting your ParcelFileDescriptor? (open()? createPipe()? something else?)Ruvolo
DbContentProvider is one I created (extends ContentProvider). It returns the ParcelFileDescriptor via openFile(). But, hang on, yes there is exception thrown... Why/how did I not spot this?! Bear with me while I take a closer look...Croon
Does your query() method get called, requesting columns from OpenableColumns, before the attempts to get at the stuff that you cited in your update (_data, datetaken, etc.)?Ruvolo
The only call to query() is for the datetaken column which, as you can see from the log which I've just appended to my question, returns a value. I've also checked that my getType() method returns a String (vnd.android.cursor.item/vnd.theholisticsworks.elemental) and my openFile() method returns a ParcelFileDescriptor ({ParcelFileDescriptor: FileDescriptor[63]});Croon
seems like I have faced this issue too. In the end I gave up. But my scenario was I was picking image from the gallery which in return give the content URI, but image is not shown. Content url was returning for the picked image but when I try to show it, its just blank.Misprision
can you show your manifest ?Misprision
Full manifest now added to question. ^^^Croon
try adding ` <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />` to your manifestMisprision
Cheers, but that hasn't helped. (I also added <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /> and <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> whilst I was at it.)Croon
I am not sure if I understood your question but why can't you give a try with FileProvider? You can check this query asked on SO for the similar issue.Opuscule
That looks very promising! I'm not familiar with FileProvider but I'll check it out in the morning...Croon
It worked a treat! And the <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> permission was not required, either - which is also good!Croon
A
2

This is an interesting problem. If your ContentProvider is exported (which is not entirely clear in the question -- it seems to be in the first snippet you posted, but not in the full AndroidManifest.xml added later) what you're doing should work. Not sure why it doesn't.

In any case, you can certainly share any image file to the Google+ app by means of a FileProvider:

FileProvider is a special subclass of ContentProvider that facilitates secure sharing of files associated with an app by creating a content:// Uri for a file instead of a file:/// Uri.

Therefore, if your image is actually a file under getFilesDir() (very likely) or can be at least moved there, then you can apply this solution.

First you must define the provider in your AndroidManifest.xml file:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.testshare.fileprovider"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>

Then this file in res\xml\filepaths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="shared_images" path="shared_images/" />
</paths>

Then, to share an image file, first make sure it's in the path specified above (i.e. shared_images under getFilesDir() -- you don't need the WRITE_EXTERNAL_STORAGE permission to copy if there because this location is inside your own app's private storage area). Then build the share intent as follows:

File file = getImageFileToShare();
Uri fileUri = FileProvider.getUriForFile(this, "com.example.testshare.fileprovider", file);

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
intent.setType("image/png");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

startActivity(intent);

(Make sure the authority specified in the getUriForFile() method matches the one in the manifest).

This will produce a content:// Uri (like content://com.example.testshare.fileprovider/shared_images/img1.png that the Google+ app will be able to access, and thus include in the post).

Artificer answered 29/1, 2015 at 14:2 Comment(4)
Another answer on Stack Overflow (https://mcmap.net/q/240767/-android-share-intent-for-a-bitmap-is-it-possible-not-to-save-it-prior-sharing) uses getCacheDir() instead of getFilesDir(). Which one do you think works best?Fulfil
Also, this would require an AsyncTask or IntentService? What do you think works best for this case? Do you have (or know of) any full code that wraps up your solution in an AsyncTask or IntentService?Fulfil
@Jamrelian You shouldn't need an AsyncTask unless getImageFileToShare() is time consuming (say, it needs to access the network). As for getFilesDir() vs getCacheDir(), it shouldn't make much of a difference.Artificer
My getImageFileToShare() uses bitmap.compress to save a bitmap to a FileOutputStream, in PNG format. The file size of the image is around 10 KB, but any file writing should not be on the UI thread?Fulfil

© 2022 - 2024 — McMap. All rights reserved.