I want to attach a file I generate on runtime.
Runtime flow:
Create intent
Create a file under cacheDir
Add file as content:// extra to the intent
Start chooser and select Gmail
expect openFile() to be called and allow access
Gmail opens with no attachment
However, openFile() isn't called.
Here is the relevant code:
Create an intent and add extras:
public static void contact(Activity activity, String message) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("text/plain");
String version = Version.appVersion(activity);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{activity.getString(R.string.contact_email)});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, activity.getString(R.string.app_name)+" ("+version+")");
emailIntent.putExtra(Intent.EXTRA_TEXT, message);
addInfoAttachment(emailIntent, activity);
String title = activity.getString(R.string.string_select_email_app);
activity.startActivity(Intent.createChooser(emailIntent, title));
}
Create the cached file:
public static void createCachedFile(Context context, String fileName,
String content) throws IOException
{
File cacheFile = new File(context.getCacheDir() + File.separator
+ fileName);
cacheFile.delete();
cacheFile.createNewFile();
FileOutputStream fos = new FileOutputStream(cacheFile);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
PrintWriter pw = new PrintWriter(osw);
pw.println(content);
pw.flush();
pw.close();
}
Add the file to the intent:
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + AUTHORITY + File.separator + REPORT_INFO_FILE_NAME));
Overriding ContentProvider: (not called)
public class LogFileProvider extends ContentProvider {
private static final String TAG = "LogFileProvider";
public static final String AUTHORITY = "com.domain.appName.LogFileProvider";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
Log.v(TAG, "openFile Called with uri: '" + uri + "'." + uri.getLastPathSegment());
// Check incoming Uri against the matcher
switch (uriMatcher.match(uri)) {
// If it returns 1 - then it matches the Uri defined in onCreate
case 1:
String fileLocation = getContext().getCacheDir() + File.separator
+ uri.getLastPathSegment();
return ParcelFileDescriptor.open(new File(
fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
default:
Log.v(TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: "
+ uri.toString());
}
}
// //////////////////////////////////////////////////////////////
// Not supported / used / required for this example
// //////////////////////////////////////////////////////////////
#Override
public int update(#NonNull Uri uri, ContentValues contentvalues, String s,
String[] as) {
return 0;
}
#Override
public int delete(#NonNull Uri uri, String s, String[] as) {
return 0;
}
#Override
public Uri insert(#NonNull Uri uri, ContentValues contentvalues) {
return null;
}
#Override
public String getType(#NonNull Uri uri) {
return null;
}
#Override
public Cursor query(#NonNull Uri uri, String[] projection, String s, String[] as1,
String s1) {
return null;
}
}
In manifest:
<provider
android:name=".LogFileProvider"
android:authorities="com.domain.appName.LogFileProvider"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true"/>
You need to add FLAG_GRANT_READ_URI_PERMISSION to the Intent. As it stands, the recipient has no rights to access that content.
You might consider using FileProvider rather than bothering with your own. Some clients will expect the provider to do more than what you have (e.g., respond to query() for the OpenableColumns, respond to getType() with the actual MIME type).
Related
I am not using sql db. a single key contains all my data as list of object.
(key:list)
How would I remove or insert or update the files and object holding paths of those files and also effect on content provider so that third party apps should know the changes and also dir, key-value storage and content provider all should synced .
Hawk storage storing the list of all Objects so I cannot access any specific object directly. For adding new object I have to store all objects in a list and add new object to list then put the list into Hawk with same key so it will replace the exisitng list.
I am using a key-value pair storage called Hawk which holds list of objects.
Objects has paths of stored files
files are stored in app specific storagei.e. CacheDir.
Content provider adds uri each time I create new Object
ContentProvider:
#Override
public boolean onCreate() {
Hawk.init(getContext()).build();
final String authority = RNWhatsAppStickersModule.getContentProviderAuthority(getContext());
if (!authority.startsWith(Objects.requireNonNull(getContext()).getPackageName())) {
throw new IllegalStateException("your authority (" + authority + ") for the content provider should start with your package name: " + getContext().getPackageName());
}
Log.d("ReactNative","authority "+authority);
//the call to get the metadata for the sticker packs.
MATCHER.addURI(authority, METADATA, METADATA_CODE);
//the call to get the metadata for single sticker pack. * represent the identifier
MATCHER.addURI(authority, METADATA + "/*", METADATA_CODE_FOR_SINGLE_PACK);
//gets the list of stickers for a sticker pack, * respresent the identifier.
MATCHER.addURI(authority, STICKERS + "/*", STICKERS_CODE);
for (StickerPack stickerPack : getStickerPackList()) {
MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + stickerPack.trayImageFile, STICKER_PACK_TRAY_ICON_CODE);
for (Sticker sticker : stickerPack.getStickers()) {
MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + sticker.imageFileName, STICKERS_ASSET_CODE);
}
}
return true;
}
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
final int code = MATCHER.match(uri);
if (code == METADATA_CODE) {
Log.d("ReactNative","METADATA_CODE "+String.valueOf(code));
return getPackForAllStickerPacks(uri);
} else if (code == METADATA_CODE_FOR_SINGLE_PACK) {
Log.d("ReactNative","METADATA_CODE_FOR_SINGLE_PACK "+String.valueOf(code));
return getCursorForSingleStickerPack(uri);
} else if (code == STICKERS_CODE) {
Log.d("ReactNative","STICKERS_CODE "+String.valueOf(code));
return getStickersForAStickerPack(uri);
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
#Nullable
#Override
public AssetFileDescriptor openAssetFile(#NonNull Uri uri, #NonNull String mode) {
final int matchCode = MATCHER.match(uri);
// if (matchCode == STICKERS_ASSET_CODE || matchCode == STICKER_PACK_TRAY_ICON_CODE) {
return getImageAsset(uri);
// return null;
}
#Override
public String getType(#NonNull Uri uri) {
final int matchCode = MATCHER.match(uri);
switch (matchCode) {
case METADATA_CODE:
return "vnd.android.cursor.dir/vnd." + RNWhatsAppStickersModule.getContentProviderAuthority(getContext()) + "." + METADATA;
case METADATA_CODE_FOR_SINGLE_PACK:
return "vnd.android.cursor.item/vnd." + RNWhatsAppStickersModule.getContentProviderAuthority(getContext()) + "." + METADATA;
case STICKERS_CODE:
return "vnd.android.cursor.dir/vnd." + RNWhatsAppStickersModule.getContentProviderAuthority(getContext()) + "." + STICKERS;
case STICKERS_ASSET_CODE:
return "image/webp";
case STICKER_PACK_TRAY_ICON_CODE:
return "image/png";
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
private synchronized void readContentFile(#NonNull Context context) {
// try (InputStream contentsInputStream = context.getAssets().open(CONTENT_FILE_NAME)) {
// stickerPackList = ContentFileParser.parseStickerPacks(contentsInputStream);
// } catch (IOException | IllegalStateException e) {
// throw new RuntimeException(CONTENT_FILE_NAME + " file has some issues: " + e.getMessage(), e);
// }
Log.d("ReactNative","readContentFile ");
if (Hawk.get("sticker_packs", new ArrayList<StickerPack>()) != null) {
stickerPackList.addAll(Hawk.get("sticker_packs", new ArrayList<StickerPack>()));
}
}
public List<StickerPack> getStickerPackList() {
// if (stickerPackList == null) {
// readContentFile(Objects.requireNonNull(getContext()));
// }
// return stickerPackList;
/*
* if (stickerPackList == null) {
* readContentFile(Objects.requireNonNull(getContext())); }
*/
Log.d("ReactNative","getStickerPackList ");
return (List) Hawk.get("sticker_packs", new ArrayList<StickerPack>());
}
private Cursor getPackForAllStickerPacks(#NonNull Uri uri) {
return getStickerPackInfo(uri, getStickerPackList());
}
private Cursor getCursorForSingleStickerPack(#NonNull Uri uri) {
final String identifier = uri.getLastPathSegment();
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
return getStickerPackInfo(uri, Collections.singletonList(stickerPack));
}
}
return getStickerPackInfo(uri, new ArrayList<StickerPack>());
}
#NonNull
private Cursor getStickerPackInfo(#NonNull Uri uri, #NonNull List<StickerPack> stickerPackList) {
MatrixCursor cursor = new MatrixCursor(
new String[]{
STICKER_PACK_IDENTIFIER_IN_QUERY,
STICKER_PACK_NAME_IN_QUERY,
STICKER_PACK_PUBLISHER_IN_QUERY,
STICKER_PACK_ICON_IN_QUERY,
ANDROID_APP_DOWNLOAD_LINK_IN_QUERY,
IOS_APP_DOWNLOAD_LINK_IN_QUERY,
PUBLISHER_EMAIL,
PUBLISHER_WEBSITE,
PRIVACY_POLICY_WEBSITE,
LICENSE_AGREENMENT_WEBSITE,
IMAGE_DATA_VERSION, // #3373
AVOID_CACHE, // #3373
ANIMATED_STICKER_PACK, // #3373
});
int i = 1;
for (StickerPack stickerPack : stickerPackList) {
Log.d("ReactNative","builder.add "+String.valueOf(i));
i++;
MatrixCursor.RowBuilder builder = cursor.newRow();
builder.add(stickerPack.identifier);
builder.add(stickerPack.name);
builder.add(stickerPack.publisher);
builder.add(stickerPack.trayImageFile);
builder.add(stickerPack.androidPlayStoreLink);
builder.add(stickerPack.iosAppStoreLink);
builder.add(stickerPack.publisherEmail);
builder.add(stickerPack.publisherWebsite);
builder.add(stickerPack.privacyPolicyWebsite);
builder.add(stickerPack.licenseAgreementWebsite);
builder.add(stickerPack.imageDataVersion); // #373
builder.add(stickerPack.avoidCache ? 1 : 0); // #373
builder.add(stickerPack.animatedStickerPack ? 1 : 0); // #373
}
cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri);
return cursor;
}
#NonNull
private Cursor getStickersForAStickerPack(#NonNull Uri uri) {
final String identifier = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(new String[]{STICKER_FILE_NAME_IN_QUERY, STICKER_FILE_EMOJI_IN_QUERY});
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
for (Sticker sticker : stickerPack.getStickers()) {
cursor.addRow(new Object[]{sticker.imageFileName, TextUtils.join(",", sticker.emojis)});
}
}
}
cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri);
return cursor;
}
private AssetFileDescriptor getImageAsset(Uri uri) throws IllegalArgumentException {
Log.d("ReactNative","getImageAsset "+uri.toString());
AssetManager am = Objects.requireNonNull(getContext()).getAssets();
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() != 3) {
throw new IllegalArgumentException("path segments should be 3, uri is: " + uri);
}
String fileName = pathSegments.get(pathSegments.size() - 1);
final String identifier = pathSegments.get(pathSegments.size() - 2);
if (TextUtils.isEmpty(identifier)) {
throw new IllegalArgumentException("identifier is empty, uri: " + uri);
}
if (TextUtils.isEmpty(fileName)) {
throw new IllegalArgumentException("file name is empty, uri: " + uri);
}
//making sure the file that is trying to be fetched is in the list of stickers.
for (StickerPack stickerPack : getStickerPackList()) {
android.util.Log.d("ReactNative","stickerPack.identifier "+stickerPack.identifier);
if (identifier.equals(stickerPack.identifier)) {
if (fileName.equals(stickerPack.trayImageFile)) {
return fetchFile(uri, am, fileName, identifier, true);
} else {
for (Sticker sticker : stickerPack.getStickers()) {
if (fileName.equals(sticker.imageFileName)) {
return fetchFile(uri, am, fileName, identifier, false);
}
}
}
}
}
return null;
}
private AssetFileDescriptor fetchFile(#NonNull Uri uri, #NonNull AssetManager am, #NonNull String fileName, #NonNull String identifier, Boolean isTrayFile) {
Log.d("ReactNative","fetchFile "+fileName);
try {
File file;
file = new File(getContext().getFilesDir() + "/" + "stickers_asset" + "/" + identifier + "/", fileName);
if (!file.exists()) {
Log.d("ReactNative", "StickerPack dir not found");
// Log.d("fetFile", "StickerPack dir not found");
}
return new AssetFileDescriptor(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY), 0L, -1L);
} catch (IOException e) {
Log.e(Objects.requireNonNull(getContext()).getPackageName(),
"IOException when getting asset file, uri:" + uri, e);
Log.d("ReactNative","IOException when getting asset file, uri:" + uri);
try {
return am.openFd("1" + "/" + "namaskar.webp");
} catch (IOException err) {
Log.e(Objects.requireNonNull(getContext()).getPackageName(), "IOException when getting asset file, uri:" + uri, err);
return null;
}
}
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Not supported");
}
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException("Not supported");
}
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException("Not supported");
}
Adding new object to Hawk Storage:
...
// first extract all stored objects by Hawk.get("sticker_packs")
List<StickerPack> temp = Hawk.get("sticker_packs");
temp.add(newStickerPack);
Hawk.put("sticker_packs", temp);
// I am new to content provider . I understood how to do CRUD with content provider +sqlite. But here only uri are being added
...
I want to update the content provider and also want to make sure that Hawk storage and physical files should be updated as well. I am worried that if I change content uri then It wont hawk storage and real files path will be different.
I am working on whatsapp stickers. I have tried this demo https://github.com/viztushar/stickers-internet .Also with the same data i am able add stickers in demo but not working in my code.
Content provide class :
public class StickerContentProvider extends ContentProvider {
/**
* Do not change the strings listed below, as these are used by WhatsApp. And changing these will break the interface between sticker app and WhatsApp.
*/
public static final String STICKER_PACK_IDENTIFIER_IN_QUERY = "sticker_pack_identifier";
public static final String STICKER_PACK_NAME_IN_QUERY = "sticker_pack_name";
public static final String STICKER_PACK_PUBLISHER_IN_QUERY = "sticker_pack_publisher";
public static final String STICKER_PACK_ICON_IN_QUERY = "sticker_pack_icon";
public static final String ANDROID_APP_DOWNLOAD_LINK_IN_QUERY = "android_play_store_link";
public static final String IOS_APP_DOWNLOAD_LINK_IN_QUERY = "ios_app_download_link";
public static final String PUBLISHER_EMAIL = "sticker_pack_publisher_email";
public static final String PUBLISHER_WEBSITE = "sticker_pack_publisher_website";
public static final String PRIVACY_POLICY_WEBSITE = "sticker_pack_privacy_policy_website";
public static final String LICENSE_AGREENMENT_WEBSITE = "sticker_pack_license_agreement_website";
public static final String STICKER_FILE_NAME_IN_QUERY = "sticker_file_name";
public static final String STICKER_FILE_EMOJI_IN_QUERY = "sticker_emoji";
public static final String CONTENT_FILE_NAME = "contents.json";
public static final String CONTENT_SCHEME = "content";
private static final String TAG = StickerContentProvider.class.getSimpleName();
public static Uri AUTHORITY_URI = new Uri.Builder().scheme(StickerContentProvider.CONTENT_SCHEME).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.METADATA).build();
/**
* Do not change the values in the UriMatcher because otherwise, WhatsApp will not be able to fetch the stickers from the ContentProvider.
*/
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static final String METADATA = "metadata";
private static final int METADATA_CODE = 1;
private static final int METADATA_CODE_FOR_SINGLE_PACK = 2;
static final String STICKERS = "stickers";
private static final int STICKERS_CODE = 3;
static final String STICKERS_ASSET = "stickers_asset";
private static final int STICKERS_ASSET_CODE = 4;
private static final int STICKER_PACK_TRAY_ICON_CODE = 5;
private List<StickerPack> stickerPackList = new ArrayList<>();
#Override
public boolean onCreate() {
Hawk.init(getContext()).build();
final String authority = BuildConfig.CONTENT_PROVIDER_AUTHORITY;
if (!authority.startsWith(Objects.requireNonNull(getContext()).getPackageName())) {
throw new IllegalStateException("your authority (" + authority + ") for the content provider should start with your package name: " + getContext().getPackageName());
}
//the call to get the metadata for the sticker packs.
MATCHER.addURI(authority, METADATA, METADATA_CODE);
//the call to get the metadata for single sticker pack. * represent the identifier
MATCHER.addURI(authority, METADATA + "/*", METADATA_CODE_FOR_SINGLE_PACK);
//gets the list of stickers for a sticker pack, * respresent the identifier.
MATCHER.addURI(authority, STICKERS + "/*", STICKERS_CODE);
for (StickerPack stickerPack : getStickerPackList()) {
Log.e(TAG, "onCreate: " + stickerPack.identifier);
MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + stickerPack.tray_image_file, STICKER_PACK_TRAY_ICON_CODE);
if (stickerPack.getStickers() != null) {
for (Sticker sticker : stickerPack.getStickers()) {
MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + sticker.image_file, STICKERS_ASSET_CODE);
}
}
}
return true;
}
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
final int code = MATCHER.match(uri);
Log.d(TAG, "query: " + code + uri);
if (code == METADATA_CODE) {
return getPackForAllStickerPacks(uri);
} else if (code == METADATA_CODE_FOR_SINGLE_PACK) {
return getCursorForSingleStickerPack(uri);
} else if (code == STICKERS_CODE) {
return getStickersForAStickerPack(uri);
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
#Nullable
#Override
public AssetFileDescriptor openAssetFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
MATCHER.match(uri);
final int matchCode = MATCHER.match(uri);
final List<String> pathSegments = uri.getPathSegments();
Log.d(TAG, "openFile: " + matchCode + uri + "\n" + uri.getAuthority()
+ "\n" + pathSegments.get(pathSegments.size() - 3) + "/"
+ "\n" + pathSegments.get(pathSegments.size() - 2) + "/"
+ "\n" + pathSegments.get(pathSegments.size() - 1));
return getImageAsset(uri);
}
private AssetFileDescriptor getImageAsset(Uri uri) throws IllegalArgumentException {
AssetManager am = Objects.requireNonNull(getContext()).getAssets();
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() != 3) {
throw new IllegalArgumentException("path segments should be 3, uri is: " + uri);
}
String fileName = pathSegments.get(pathSegments.size() - 1);
final String identifier = pathSegments.get(pathSegments.size() - 2);
if (TextUtils.isEmpty(identifier)) {
throw new IllegalArgumentException("identifier is empty, uri: " + uri);
}
if (TextUtils.isEmpty(fileName)) {
throw new IllegalArgumentException("file name is empty, uri: " + uri);
}
//making sure the file that is trying to be fetched is in the list of stickers.
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
if (fileName.equals(stickerPack.tray_image_file)) {
return fetchFile(uri, am, fileName, identifier);
} else {
for (Sticker sticker : stickerPack.getStickers()) {
if (fileName.equals(sticker.image_file)) {
return fetchFile(uri, am, fileName, identifier);
}
}
}
}
}
return null;
}
private AssetFileDescriptor fetchFile(#NonNull Uri uri, #NonNull AssetManager am, #NonNull String fileName, #NonNull String identifier) {
try {
File file;
if(fileName.endsWith(".png")){
file = new File(getContext().getFilesDir()+ "/" + "stickers_asset" + "/" + identifier + "/try/", fileName);
} else {
file = new File(getContext().getFilesDir()+ "/" + "stickers_asset" + "/" + identifier + "/", fileName);
}
if (!file.exists()) {
Log.d("fetFile", "StickerPack dir not found");
}
Log.d("fetchFile", "StickerPack " + file.getPath());
return new AssetFileDescriptor(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY), 0L, -1L);
} catch (IOException e) {
Log.e(Objects.requireNonNull(getContext()).getPackageName(), "IOException when getting asset file, uri:" + uri, e);
return null;
}
}
#Override
public String getType(#NonNull Uri uri) {
final int matchCode = MATCHER.match(uri);
switch (matchCode) {
case METADATA_CODE:
return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA;
case METADATA_CODE_FOR_SINGLE_PACK:
return "vnd.android.cursor.item/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA;
case STICKERS_CODE:
return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + STICKERS;
case STICKERS_ASSET_CODE:
return "image/webp";
case STICKER_PACK_TRAY_ICON_CODE:
return "image/png";
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
private synchronized void readContentFile(#NonNull Context context) {
if (Hawk.get("sticker_pack", new ArrayList<StickerPack>()) != null) {
stickerPackList.addAll(Hawk.get("sticker_pack", new ArrayList<StickerPack>()));
}
}
public List<StickerPack> getStickerPackList() {
/* if (stickerPackList == null) {
readContentFile(Objects.requireNonNull(getContext()));
}*/
return (List)Hawk.get("sticker_packs",new ArrayList<StickerPack>());
}
private Cursor getPackForAllStickerPacks(#NonNull Uri uri) {
return getStickerPackInfo(uri, getStickerPackList());
}
private Cursor getCursorForSingleStickerPack(#NonNull Uri uri) {
final String identifier = uri.getLastPathSegment();
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
return getStickerPackInfo(uri, Collections.singletonList(stickerPack));
}
}
return getStickerPackInfo(uri, new ArrayList<StickerPack>());
}
#NonNull
private Cursor getStickerPackInfo(#NonNull Uri uri, #NonNull List<StickerPack> stickerPackList) {
MatrixCursor cursor = new MatrixCursor(
new String[]{
STICKER_PACK_IDENTIFIER_IN_QUERY,
STICKER_PACK_NAME_IN_QUERY,
STICKER_PACK_PUBLISHER_IN_QUERY,
STICKER_PACK_ICON_IN_QUERY,
ANDROID_APP_DOWNLOAD_LINK_IN_QUERY,
IOS_APP_DOWNLOAD_LINK_IN_QUERY,
PUBLISHER_EMAIL,
PUBLISHER_WEBSITE,
PRIVACY_POLICY_WEBSITE,
LICENSE_AGREENMENT_WEBSITE
});
for (StickerPack stickerPack : stickerPackList) {
MatrixCursor.RowBuilder builder = cursor.newRow();
builder.add(stickerPack.identifier);
builder.add(stickerPack.name);
builder.add(stickerPack.publisher);
builder.add(stickerPack.tray_image_file);
builder.add(stickerPack.androidPlayStoreLink);
builder.add(stickerPack.iosAppStoreLink);
builder.add(stickerPack.publisher_email);
builder.add(stickerPack.publisher_website);
builder.add(stickerPack.privacy_policy_website);
builder.add(stickerPack.license_agreement_website);
}
Log.d(TAG, "getStickerPackInfo: " + stickerPackList.size());
cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri);
return cursor;
}
#NonNull
private Cursor getStickersForAStickerPack(#NonNull Uri uri) {
final String identifier = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(new String[]{STICKER_FILE_NAME_IN_QUERY, STICKER_FILE_EMOJI_IN_QUERY});
for (StickerPack stickerPack : getStickerPackList()) {
if (identifier.equals(stickerPack.identifier)) {
for (Sticker sticker : stickerPack.getStickers()) {
cursor.addRow(new Object[]{sticker.image_file, TextUtils.join(",", sticker.emojis)});
}
}
}
cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri);
return cursor;
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Not supported");
}
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException("Not supported");
}
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException("Not supported");
}
}
Please let me know if any other code require.
Error trace :
IOException when getting asset file, uri:content://com.mercatus.shoppers.stag.provider.StickerContentProvider/stickers_asset/1/69DEA9206D7A4EDF8D8E6439B41BBBB4.ashx
java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)
at android.os.ParcelFileDescriptor.openInternal(ParcelFileDescriptor.java:313)
at android.os.ParcelFileDescriptor.open(ParcelFileDescriptor.java:211)
at com.mall.provider.StickerContentProvider.fetchFile(StickerContentProvider.java:187)
at com.mall.provider.StickerContentProvider.getImageAsset(StickerContentProvider.java:162)
at com.mall.provider.StickerContentProvider.openAssetFile(StickerContentProvider.java:140)
at android.content.ContentProvider.openTypedAssetFile(ContentProvider.java:1746)
at android.content.ContentProvider.openTypedAssetFile(ContentProvider.java:1812)
at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:425)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:302)
at android.os.Binder.execTransact(Binder.java:739)
Error :
File location :
I have an application which open pdf file with tap on app (default PDF in raw folder), openwith (myapp) and also from mail attachment. I converted the selected PDF file into inputstream and then to bytearray. Now I have a button and when user click on it need to send email through gmail app using intent. I need to add inputstream or bytearray as pdffile to the mail as attachment.I used the following code I can see the attachment with no bytes and received email donot have attachment. Struggling from more than 10 hours.. please help...
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("application/pdf");
sendIntent.setData(Uri.parse("sample#gmailcom"));
sendIntent.setClassName("com.google.android.gm", "com.google.android.gm.ComposeActivityGmail");
sendIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { "sample#gmail.com" });
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "testPDF");
sendIntent.putExtra(Intent.EXTRA_TEXT, "this is a PDF ");
File fileIn = new File(name, "myfile.pdf");
Toast.makeText(this, fileIn.getName(), Toast.LENGTH_LONG).show();
Uri fileuri = Uri.fromFile(fileIn);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileuri.toString()));
startActivity(sendIntent);
I tried many ways and finally found a solutions myself. Use file provider to attach the file to mail without storing it physically on the device. The code is follows and hope it helps someone else.
Add a class AttachFileProvider with below code
public class AttachFileProvider extends ContentProvider {
private static final String CLASS_NAME = "AttachFileProvider";
// The authority is the symbolic name for the provider class
public static final String AUTHORITY = "com.example.pDoc.pdocsigner.provider";
// UriMatcher used to match against incoming requests<br />
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add a URI to the matcher which will match against the form
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// Check incoming Uri against the matcher
switch (uriMatcher.match(uri)) {
// If it returns 1 - then it matches the Uri defined in onCreate
case 1:
// The desired file name is specified by the last segment of the path
// Take this and build the path to the file
String fileLocation = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
// Create & return a ParcelFileDescriptor pointing to the file
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
// Otherwise unrecognised Uri
default:
//Log.v(LOG_TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
// //////////////////////////////////////////////////////////////
// Not supported / used / required for this example
// //////////////////////////////////////////////////////////////
#Override
public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { return 0; }
#Override
public int delete(Uri uri, String s, String[] as) { return 0; }
#Override
public Uri insert(Uri uri, ContentValues contentvalues) { return null; }
#Override
public String getType(Uri uri) { return null; }
#Override
public Cursor query(Uri uri, String[] projection, String s, String[] as1, String s1) { return null; }
}
to attach the mail use the following code.
if (bytes.length > 0) {
try {
createCachedFile(this, attachmentFileName, bytes);
this.startActivity(getSendEmailIntent(this, "tomailid", "Test", "See attached", attachmentFileName));
} catch (IOException e) {
e.printStackTrace();
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "Gmail is not available on this device.", Toast.LENGTH_SHORT).show();
}
}
public void createCachedFile(Context context, String fileName, byte[] content) throws IOException {
File cacheFile = new File(context.getCacheDir() + File.separator + fileName);
cacheFile.createNewFile();
BufferedOutputStream bostream = new BufferedOutputStream(new FileOutputStream(cacheFile));
bostream.write(content);
bostream.flush();
bostream.close();
}
public static Intent getSendEmailIntent(Context context, String email, String subject, String body, String fileName) {
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
//Explicitly only use Gmail to send
// emailIntent.setClassName("com.google.android.gm","com.google.android.gm.ComposeActivityGmail");
//emailIntent.setType("message/rfc822");
emailIntent.setType("vnd.android.cursor.item/email");
//Add the recipients
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { email });
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, body);
//Add the attachment to custom ContentProvider
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + AttachFileProvider.AUTHORITY + "/" + fileName));
return emailIntent;
}
You should make sure that you attachment file can be read by gmail app, so put it in the external sdcard storage area. Maybe you path has problem, change it and test.
I convert a bitmap image (bm) into jpeg and stored an image in internal phone memory using
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, outputBuffer);
byte[] byteImage1=outputBuffer.toByteArray();
//save file to internal storage
FileOutputStream outputStream;
try {
outputStream = context.openFileOutput("myphoto.jpg", Context.MODE_PRIVATE);
outputStream.write(byteImage1);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
now i want to share the stored image using shareIntent
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpeg");
share.putExtra(Intent.EXTRA_STREAM,
Uri.parse(context.getFilesDir().getPath() + "myphoto.jpg"));
context.startActivity(Intent.createChooser(share, "Partager par"));
But the intent is not running when i pressed click button. Please lend a help on this issue.
You will need to write Content Providers to share image in internal storage
See Sharing images that are stored on internal memory
public class MediaContentProvider extends ContentProvider {
private static final String TAG = "MediaContentProvider";
// name for the provider class
public static final String AUTHORITY = "com.way.srl.HandyWay.contentproviders.media";
private MediaData _mediaData;
// UriMatcher used to match against incoming requests
private UriMatcher _uriMatcher;
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
#Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add a URI to the matcher which will match against the form
// 'content://com.stephendnicholas.gmailattach.provider/*'
// and return 1 in the case that the incoming Uri matches this pattern
_uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Log.v(TAG, "Called with uri: '" + uri + "'." + uri.getLastPathSegment());
// Check incoming Uri against the matcher
switch (_uriMatcher.match(uri)) {
// If it returns 1 - then it matches the Uri defined in onCreate
case 1:
// The desired file name is specified by the last segment of the
// path
// E.g.
// 'content://com.stephendnicholas.gmailattach.provider/Test.txt'
// Take this and build the path to the file
// String fileLocation = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
Integer mediaID = Integer.valueOf(uri.getLastPathSegment());
if (_mediaData == null) {
_mediaData = new MediaData();
}
Media m = _mediaData.get(mediaID);
// Create & return a ParcelFileDescriptor pointing to the file
// Note: I don't care what mode they ask for - they're only getting
// read only
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(m.filePath), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
// Otherwise unrecognised Uri
default:
Log.v(TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
Similar threads here do not have answers that helped...
I want to create email message with file attach, file is on internal storage.
Here is code:
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_EMAIL , new String[]{email});
i.putExtra(Intent.EXTRA_SUBJECT, "Smart Weight Tracker");
i.putExtra(Intent.EXTRA_TEXT , "CSV file is in attachment");
Uri uri;
if(useExternal){
uri = Uri.fromFile(new File(this.getExternalFilesDir(null),fname));
}
else{
File f = new File(this.getFilesDir(),fname);
f.setReadable(true, false);
f.setWritable(true, false);
uri = Uri.fromFile(f);
}
i.putExtra(Intent.EXTRA_STREAM, uri);
try {
startActivity(Intent.createChooser(i, "Send mail..."));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(MainActivity.this, "There are no email clients installed.", Toast.LENGTH_SHORT).show();
}
It works perfectly with external storage, but i have no attach when use internal storage.
I checked - file opens, (it's length in my app by showing Toast - is OK, > 0).
I write it this way:
OutputStreamWriter out =
new OutputStreamWriter(con.openFileOutput(fname, Context.MODE_WORLD_READABLE));
In logcat i see
I/Gmail (28480): >>>>> Attachment uri: file:///data/data/Android.MyApp/files /31.10.2011.csv
I/Gmail (28480): >>>>> type: text/plain
I/Gmail (28480): >>>>> name: 31.10.2011.csv
I/Gmail (28480): >>>>> size: 0
Size == 0!
Any ideas?
Hi,
Try to use content providers.
emailIntent.putExtra(Intent.EXTRA_STREAM,Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/"+ fileName));
........
Android: Attaching files from internal cache to Gmail
package com.stephendnicholas.gmailattach;
import java.io.File;
import java.io.FileNotFoundException;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class CachedFileProvider extends ContentProvider {
private static final String CLASS_NAME = "CachedFileProvider";
// The authority is the symbolic name for the provider class
public static final String AUTHORITY = "com.stephendnicholas.gmailattach.provider";
// UriMatcher used to match against incoming requests
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add a URI to the matcher which will match against the form
// 'content://com.stephendnicholas.gmailattach.provider/*'
// and return 1 in the case that the incoming Uri matches this pattern
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
String LOG_TAG = CLASS_NAME + " - openFile";
Log.v(LOG_TAG,
"Called with uri: '" + uri + "'." + uri.getLastPathSegment());
// Check incoming Uri against the matcher
switch (uriMatcher.match(uri)) {
// If it returns 1 - then it matches the Uri defined in onCreate
case 1:
// The desired file name is specified by the last segment of the
// path
// E.g.
// 'content://com.stephendnicholas.gmailattach.provider/Test.txt'
// Take this and build the path to the file
String fileLocation = getContext().getCacheDir() + File.separator
+ uri.getLastPathSegment();
// Create & return a ParcelFileDescriptor pointing to the file
// Note: I don't care what mode they ask for - they're only getting
// read only
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(
fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
// Otherwise unrecognised Uri
default:
Log.v(LOG_TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: "
+ uri.toString());
}
}
// //////////////////////////////////////////////////////////////
// Not supported / used / required for this example
// //////////////////////////////////////////////////////////////
#Override
public int update(Uri uri, ContentValues contentvalues, String s,
String[] as) {
return 0;
}
#Override
public int delete(Uri uri, String s, String[] as) {
return 0;
}
#Override
public Uri insert(Uri uri, ContentValues contentvalues) {
return null;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String s, String[] as1,
String s1) {
return null;
}
}
<provider android:name="CachedFileProvider" android:authorities="com.stephendnicholas.gmailattach.provider">
public static void createCachedFile(Context context, String fileName,
String content) throws IOException {
File cacheFile = new File(context.getCacheDir() + File.separator
+ fileName);
cacheFile.createNewFile();
FileOutputStream fos = new FileOutputStream(cacheFile);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
PrintWriter pw = new PrintWriter(osw);
pw.println(content);
pw.flush();
pw.close();
}
public static Intent getSendEmailIntent(Context context, String email,
String subject, String body, String fileName) {
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
//Explicitly only use Gmail to send
emailIntent.setClassName("com.google.android.gm","com.google.android.gm.ComposeActivityGmail");
emailIntent.setType("plain/text");
//Add the recipients
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
new String[] { email });
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, body);
//Add the attachment by specifying a reference to our custom ContentProvider
//and the specific file of interest
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/"
+ fileName));
return emailIntent;
}
http://stephendnicholas.com/archives/974#comment-342
Uri fileUri = Uri.fromFile(new File(context.getCacheDir()+ "/"+ fileName));
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
"Test Subject");
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT,
"go on read the emails");
emailIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
startActivity(emailIntent);
Please try this code. Hope this would help.
I was following the stephendnicholas code for hours thinking I HAD to write my files to internal storage because my device had no sd card.
Creating my "internal" file like this worked just fine for me:
File file = new File(getExternalFilesDir(null), filename); // :)
It avoided Gmail limitations during attachment.
Before this I was creating my file object like this:
File file = new File(this.getFilesDir(), filename); //will not attach
But if your file is literally from internal storage, then stephendnicholas content providers etc. work if you have mad android skills.