Chandru Chandru - 4 months ago 17
Android Question

Permisssion denied while attaching apk file with GMail app android

I have a requirement to send a apk file through share Intent and I have also implemented without any hassle. But the problem arises only while sending apk via GMail, I am getting permission denied while i try to attach. I really dont know what is happening with GMail. Kindly help me through your answers and solutions. Any Small hint and tips would also be useful for me. Thanks in advance

I am using following code to send APK using shared intents:

public static void appSend(ArrayList<File> files,Context context){
Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
shareIntent.setType("application/vnd.android.package-archive");
shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
ArrayList<Uri> uriFiles = new ArrayList<Uri> ();
for(File file: files) {
uriFiles.add (Uri.fromFile (file));
}
shareIntent.putParcelableArrayListExtra (Intent.EXTRA_STREAM, uriFiles);

try {
context.startActivity (Intent.createChooser (shareIntent, "Share via"));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText (context, "There are no share applications installed.", Toast.LENGTH_SHORT).show();
}
}


Androidmanifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.share"
android:versionCode="2"
android:versionName="1.1">

<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="ANDROID.PERMISSION.READ_EXTERNAL_STORAGE"/>

<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity
android:name="com.sample.share.SplashActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name="com.sample.share.LandingScreenActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"/>

<activity
android:name="com.sample.share.MainActivity"
android:label="@string/app_name"
android:theme="@style/ShareToolbar"/>
</application>

</manifest>


Adapter class to fetch the list of apps present in the device

ShowAppsAdapter.java

public class ShowAppsAdapter extends RecyclerView.Adapter<ShowAppsAdapter.ViewHolder> implements Filterable{
private Context context;
private static List pkgAppsList;
private List pkgAppsListOriginal;
private int mLayout;
private ArrayList<File> mFileList;
static OnItemClickListener mItemClickListener;

private ItemFilter filter = new ItemFilter();
private HashMap<String,Boolean> itemChecked = new HashMap<String,Boolean>();
private Typeface tf;

public ShowAppsAdapter(Context context, List pkgAppsList, int layout, Typeface tf) {
this.context = context;
this.pkgAppsList = pkgAppsList;
this.pkgAppsListOriginal = pkgAppsList;
this.mLayout = layout;
this.mFileList = new ArrayList<File> ();
this.tf = tf;

for (int i = 0; i < this.getItemCount (); i++) {
itemChecked.put ((String) ((ResolveInfo) pkgAppsList.get (i)).loadLabel (context.getPackageManager ()), false); // initializes all items value with false
}
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

View v = LayoutInflater.from(viewGroup.getContext()).inflate(mLayout, viewGroup, false);


return new ViewHolder(v);
}




@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {

final ResolveInfo info = (ResolveInfo) pkgAppsList.get(position);
final File file = new File(info.activityInfo.applicationInfo.publicSourceDir);
String appSize= (Utils.getFileSize(context, info.activityInfo.applicationInfo.sourceDir));

holder.txtAppName.setText(info.loadLabel(context.getPackageManager()));


holder.txtAppSize.setText(/*info.activityInfo.packageName*/""+appSize);

holder.txtAppName.setTypeface(tf);
holder.txtAppSize.setTypeface(tf);

holder.txtAppIcon.setImageDrawable(info.loadIcon(context.getPackageManager()));

holder.chkTick.setChecked(itemChecked.get(info.loadLabel (context.getPackageManager ())));
holder.chkTick.setOnClickListener (new View.OnClickListener () {
@Override
public void onClick (View view) {

toggleSelected((String) info.loadLabel (context.getPackageManager ()), holder.chkTick.isChecked(), file);

}
});
}


private void toggleSelected (String name, boolean b, File file) {
itemChecked.put (name,b);
if(b){
addAppList(file);
}else{
removeAppList(file);
}
}


private void addAppList (File file) {

mFileList.add (file);
}

private void removeAppList (File file) {

mFileList.remove (file);
}

public ArrayList<File> getAppList(){


return mFileList;
}

@Override
public int getItemCount() {
return pkgAppsList == null ? 0 : pkgAppsList.size();
}

@Override
public Filter getFilter () {
return filter;
}




public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView txtAppName;
TextView txtAppSize;
ImageView txtAppIcon;
CheckBox chkTick;

public ViewHolder(View itemView) {
super(itemView);
txtAppName = (TextView)itemView.findViewById(R.id.text_app_name);
txtAppSize = (TextView)itemView.findViewById(R.id.txt_app_size);
txtAppIcon = (ImageView)itemView.findViewById(R.id.img_app_icon);
chkTick = (CheckBox)itemView.findViewById (R.id.chk_tick);
itemView.setOnClickListener(this);

}



@Override
public void onClick(View v) {
mItemClickListener.onItemClick(v, getAdapterPosition (),((ResolveInfo)pkgAppsList.get (getAdapterPosition ()))); //OnItemClickListener mItemClickListener;
}

}


public interface OnItemClickListener {
public void onItemClick (View view, int position, ResolveInfo o);
}

public void SetOnItemClickListener(final OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
@Override
public long getItemId(int i) {
return i;
}


private class ItemFilter extends Filter{
@Override
protected FilterResults performFiltering (CharSequence charSequence) {
String searchString = charSequence.toString ().toLowerCase ();
FilterResults results = new FilterResults();
final List list = pkgAppsListOriginal;

int count = list.size();
final List nlist = new ArrayList<ResolveInfo>(count);

String filterableString ;

for (int i = 0; i < count; i++) {
final ResolveInfo info = (ResolveInfo) pkgAppsListOriginal.get(i);
filterableString = (String) info.loadLabel (context.getPackageManager ());
if (filterableString.toLowerCase().contains(searchString)) {
nlist.add(pkgAppsListOriginal.get (i));
}
}

results.values = nlist;
results.count = nlist.size();

return results;
}

@Override
protected void publishResults (CharSequence charSequence, FilterResults filterResults) {
pkgAppsList = (List) filterResults.values;
notifyDataSetChanged ();
}
}
}

Answer

When sharing files between applications, you should use a FileProvider as per the setting up file sharing training as this ensures that the receiving app can read the file without requiring any permissions. As of Android 6.0, Gmail does not request the storage permission, meaning it is unable to read any files you provide with Uri.fromFile().

You'd declare a FileProvider in your manifest:

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

An XML file (in this case called filepaths.xml to match the above manifest declaration) which determines which directories are available via the FileProvider:

<paths>
    <files-path path="images/" name="myimages" />
</paths>

Then, in place of using Uri.fromFile(file), you use FileProvider.getUriForFile(), passing in the same authority as in your manifest.