I've followed the tutorial on the cloud print website and created a Print activity by copy and pasting the example code.
I'm trying to print an image from the MediaStore but when I get as far as the print screen nothing happens after I press the 'Print' button.
This is the code I'm using to call the intent
Intent printIntent = new Intent(GalleryActivity.this, PrintDialogActivity.class);
Uri fileUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Long.toString(imageId));
Log.d(this, "File Uri:" + fileUri);
printIntent.setDataAndType(fileUri, "image/*");
startActivity(printIntent);
The Uri being logged looks like content://media/external/images/media/26848
The Logcat output when I press the print button is
[INFO:CONSOLE(1)] "Uncaught TypeError: Object [object Object] has no method 'getType'", source: https://www.google.com/cloudprint/dialog.html (1)
[INFO:CONSOLE(280)] "Uncaught TypeError: Cannot call method 'k' of null", source: https://www.google.com/cloudprint/client/442365700-dialog_mobile.js (280)
Edit: I've tested on a couple of other devices and I don't get the above log output, so it may not be related. However, the result is the same on every device; when I press the print button in the webview nothing happens.
Add the #JavascriptInterface in the methods of PrintDialogJavaScriptInterface class.
final class PrintDialogJavaScriptInterface {
#JavascriptInterface
public String getType() {
return cloudPrintIntent.getType();
}
#JavascriptInterface
public String getTitle() {
return cloudPrintIntent.getExtras().getString("title");
}
#JavascriptInterface
public String getContent() {
try {
ContentResolver contentResolver = getContentResolver();
InputStream is = contentResolver.openInputStream(cloudPrintIntent.getData());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = is.read(buffer);
while (n >= 0) {
baos.write(buffer, 0, n);
n = is.read(buffer);
}
is.close();
baos.flush();
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
#JavascriptInterface
public String getEncoding() {
return CONTENT_TRANSFER_ENCODING;
}
#JavascriptInterface
public void onPostMessage(String message) {
if (message.startsWith(CLOSE_POST_MESSAGE_NAME)) {
finish();
}
}
}
Related
Typical Scenario
In order to upload a file using WebView, it's typically needed to override WebChromeClient, and start a file chooser Activity for result:
...
webView.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
mFilePathCallback = filePathCallback;
Intent intent = new Intent(MainActivity.this, FilePickerActivity.class);
startActivityForResult(intent, 1);
return true;
}
});
...
Then, once file is selected (for simplicity, only a single file can be selected at a time), onActivityResult() is called with a file uri stored in a data object. So, the uri is retrieved and handed over to filePathCallback and the file gets uploaded:
...
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if (requestCode == 1) {
mFilePathCallback.onReceiveValue(new Uri[] {data.getData()});
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
...
Basically, filePathCallback requires a uri to a file so that to upload it.
The problem
What if I have only an InputStream that contains the data I need to upload rather than a file with a URI? And I cannot save that data to a file so that to generate a URI (for security reasons). Is there a way to upload the data as a file in such case? In particular, can I convert an InputStream to a content URI and then upload it?
Approach 1
This approach works fine, if uri is generated using Uri.fromFile() as below:
...
Uri uri = Uri.fromFile(file);
...
Approach 2
If I implement my own ContentProvider and override openFile there in such a way, that it uses ParcelFileDescriptor.open() to create a ParcelFileDescriptor, then uploading a file based on a uri provided by getContentUri(...) is working without problems:
FileUploadProvider.java
public class FileUploadProvider extends ContentProvider {
public static Uri getContentUri(String name) {
return new Uri.Builder()
.scheme("content")
.authority(PROVIDER_AUTHORITY)
.appendEncodedPath(name)
.appendQueryParameter(OpenableColumns.DISPLAY_NAME, name)
.build();
}
#Nullable
#Override
public ParcelFileDescriptor openFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
List<String> segments = uri.getPathSegments();
File file = new File(getContext().getApplicationContext().getCacheDir(),
TextUtils.join(File.separator, segments));
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
...
}
Approach 3
However, if I create a ParcelFileDescriptor with help of ParcelFileDescriptor.createPipe(), then file upload never finishes, so it basically doesn't work:
...
public ParcelFileDescriptor openFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
List<String> segments = uri.getPathSegments();
File file = new File(getContext().getApplicationContext().getCacheDir(),
TextUtils.join(File.separator, segments));
InputStream inputStream = new FileInputStream(file);
ParcelFileDescriptor[] pipe;
try {
pipe = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
throw new RuntimeException(e);
}
ParcelFileDescriptor readPart = pipe[0];
ParcelFileDescriptor writePart = pipe[1];
try {
IOUtils.copy(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writePart));
} catch (IOException e) {
throw new RuntimeException(e);
}
return readPart;
}
...
Approach 4
To make matters worse, if I create a ParcelFileDescriptor with help of MemoryFile, then file upload never finishes as well:
...
public ParcelFileDescriptor openFile(#NonNull Uri uri, #NonNull String mode) throws FileNotFoundException {
List<String> segments = uri.getPathSegments();
File file = new File(getContext().getApplicationContext().getCacheDir(),
TextUtils.join(File.separator, segments));
try {
MemoryFile memoryFile = new MemoryFile(file.getName(), (int) file.length());
byte[] fileBytes = Files.readAllBytes(file.toPath());
memoryFile.writeBytes(fileBytes, 0, 0, (int) file.length());
Method method = memoryFile.getClass().getDeclaredMethod("getFileDescriptor");
FileDescriptor fileDescriptor = (FileDescriptor) method.invoke(memoryFile);
Constructor<ParcelFileDescriptor> constructor = ParcelFileDescriptor.class.getConstructor(FileDescriptor.class);
ParcelFileDescriptor parcelFileDescriptor = constructor.newInstance(fileDescriptor);
return parcelFileDescriptor;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
...
Why is file upload not working for Approach 3 and Approach 4?
Example
I created a sample app to showcase my issue here.
Below are steps to follow to reproduce it. First log in to your gmail account and click "create new message".
Solution
My colleagues have found a solution to this with help of StorageManager - please read more about the component here.
First, create a callback that handles file system requests from ProxyFileDescriptor in FileUploadProvider:
private static class CustomProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
private ByteArrayInputStream inputStream;
private long length;
public CustomProxyFileDescriptorCallback(File file) {
try {
byte[] fileBytes = Files.readAllBytes(file.toPath());
length = fileBytes.length;
inputStream = new ByteArrayInputStream(fileBytes);
} catch (IOException e) {
// do nothing here
}
}
#Override
public long onGetSize() {
return length;
}
#Override
public int onRead(long offset, int size, byte[] out) {
inputStream.skip(offset);
return inputStream.read(out,0, size);
}
#Override
public void onRelease() {
try {
inputStream.close();
} catch (IOException e) {
//ignore this for now
}
inputStream = null;
}
}
Then, create a file descriptor using StorageManager that will read the InputStream with help of the callback above.
...
StorageManager storageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
ParcelFileDescriptor descriptor;
try {
descriptor = storageManager.openProxyFileDescriptor(
ParcelFileDescriptor.MODE_READ_ONLY,
new CustomProxyFileDescriptorCallback(file),
new Handler(Looper.getMainLooper()));
} catch (IOException e) {
throw new RuntimeException(e);
}
return descriptor;
...
Please find the full code on github here.
Limitation
This approach is working only for Android API higher than 25.
I'm working on an app where I'm downloading a PDF file, saving it to internal storage and then opening that file in other app using FileProvider.
Note: It may be a duplicate question, I've gone through most of the questions on StackOverflow, but still didn't find the solution.
The file is getting downloaded fine but when I'm opening it, it is empty.
The downlaoded file is 30 kb and it has 5 pages but all are empty.
Initially, I thought it is empty because the other app doesn't have permission to open the file, but I did another thing to check whether it is a permission issue. I've saved the file to external storage, still, it was empty. So, it means it is not a permission issue.
Please Note:
Along with pdf file, there is some .xls file as well and when I'm opening those in excel android app, it says cannot open the file. This indicates, that there is some issue while writing the byte stream.
Retrofit Interface.java
#GET(ApiConstants.END_POINT_DOWNLOAD_DOCUMENT)
#Streaming
Call<ResponseBody> downloadDocument(#Query("bucket") String bucket, #Query("filename") String fileName);
Code to Download the file: Here I'm checking if a file is already there, then return the file, otherwise download the file.
public LiveData<Resource<File>> openOrDownloadFile(String bucket, String fileName) {
MutableLiveData<Resource<File>> documentLiveData = new MutableLiveData<>();
documentLiveData.postValue(Resource.loading(null));
Context context = MyApp.getInstance();
final File file = new File(context.getFilesDir(), fileName);
if (file.exists()) {
documentLiveData.postValue(Resource.success(file));
} else {
Call<ResponseBody> call = apiService.downloadDocument(bucket, fileName);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
appExecutors.diskIO().execute(new Runnable() {
#Override
public void run() {
try {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
inputStream = response.body().byteStream();
outputStream = new FileOutputStream(file);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
}
documentLiveData.postValue(Resource.success(file));
outputStream.flush();
} catch (IOException e) {
documentLiveData.postValue(Resource.error("Error: Unable to save file/n"+e.getLocalizedMessage(), null));
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
Log.e(AppConstants.TAG, e.getMessage(), e);
documentLiveData.postValue(Resource.error("Error: Unable to save file/n"+e.getLocalizedMessage(), null));
}
}
});
} else {
documentLiveData.postValue(Resource.error("Unable to download file", null));
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
documentLiveData.postValue(Resource.error(t.getLocalizedMessage(), null));
}
});
}
return documentLiveData;
}
Fragment Code
private void onItemClickListener(Document document) {
mDocumentsViewModel.openORDownloadFile(document.getType(), document.getName()).observe(this, new Observer<Resource<File>>() {
#Override
public void onChanged(#Nullable Resource<File> fileResource) {
binding.setResource(fileResource);
if (fileResource.status == Status.SUCCESS) {
openFile(fileResource.data);
}
}
});
}
void openFile(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID, file);
intent.setDataAndType(uri, mDocumentsViewModel.getMimeType(file.getAbsolutePath()));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
PackageManager pm = getActivity().getPackageManager();
if (intent.resolveActivity(pm) != null) {
startActivity(intent);
} else {
Toast.makeText(getActivity(), "This file cannot be opened on this device. Please download some compatible app from play store", Toast.LENGTH_SHORT).show();
}
}
Following are the versions :
ext.retrofit_version = "2.4.0"
ext.okhttp_version = "3.8.0"
I'm struggling with this issue, it'll be a great help if you can point out the issue. Thank you.
Update: The problem was with the backend APIs. My code was correct. Once they've fixed the problem at there side, it started working at my side without any changes.
Intend:
I´m trying to develop a single-purpose app, which you can not exit. It should run on a phone as an "operating system". This phone will be used for further purposes. The app will not be in the play store and also you can´t leave it so I need to update it otherwise if I have newer versions. For this purpose I´ve written another app which you could call the "updater-app". I want to do this update via Bluetooth. I already prepared everything and it´s ready for file transfer. The phones can connect by an InsecureRfcommSocket. I also managed to select the .apk file which I want to send with a chooser on the updater-app and I´m converting the uri into bytes in this Code:
sendUpdateButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mBtService != null) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("application/vnd.android.package-archive");
intent.addCategory(Intent.CATEGORY_OPENABLE);
try {
startActivityForResult(Intent.createChooser(intent, "Choose .apk"), FILE_SELECT_CODE);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(getApplicationContext(), "No File-Explorer found", Toast.LENGTH_SHORT).show();
}
}
}
});
and the onActivityResult:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_SELECT_CODE && resultCode == RESULT_OK && data != null) {
Uri selectedFile = data.getData();
try {
byte[] sendingByte = readBytes(selectedFile);
mBtService.send(sendingByte);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), R.string.transmissionFailed, Toast.LENGTH_SHORT).show();
}
}
}
the readBytes function:
public byte[] readBytes(Uri uri) throws IOException {
InputStream inputStream = getContentResolver().openInputStream(uri);
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
return byteBuffer.toByteArray();
}
with the mBtService.send(sendingByte) row the following function is called in the ConnectedThread:
public void send(byte[] selectedFile) {
try {
mmOutStream.write(selectedFile);
} catch (IOException e) {
e.printStackTrace();
}
}
Problem:
In the receiver phone I´ve got no idea how to receive the bytes and convert it back to a file/uri (no idea yet) .apk and save it to my phone to execute it with another button which is not part of this question.
Question:
So my question is what to to in the code of the receiver phone app to manage finish my intend? If I missed to post any relevant code I´m sorry and will add it on your Request. As I have to program this both apps for my studies I´m thankful for any help.
I found a Solution to save the received Bytes to a ByteArray and then save it to a File. In my Bluetooth ConnectedThread I have the following run() method:
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int len;
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
try {
while ((len = mmInStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
mHandler.obtainMessage(Constants.MESSAGE_READ, len, -1, byteBuffer.toByteArray())
.sendToTarget();
}
} catch (IOException e) {
e.printStackTrace();
connectionLost();
}
}
And in my Activity I have the following Handler MESSAGE_READ:
private final Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MESSAGE_STATE_CHANGE:
...
break;
case Constants.MESSAGE_READ:
byte[] buffer = (byte[]) msg.obj;
try {
String filePath = Environment.getExternalStorageDirectory() + "/download/app.apk";
File file = new File(filePath);
file.getParentFile().mkdirs();
if (file.exists()) {
file.delete();
file.createNewFile();
}
else
file.createNewFile();
BufferedOutputStream objectOut = new BufferedOutputStream(new FileOutputStream(file));
objectOut.write(buffer);
objectOut.close();
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
};
With this Code the .apk is saved to the regular Download folder as "app.apk".
Hope this is helpful for anyone.
My AsyncTask is logging an error "println needs a message" however no exception is being thrown in my class. The task is started in an Activity which implements a callback interface I wrote called TaskCallback. In the onPostExecute() it calles the callback in the Activity. From this callback, I run another AsyncTask. Below is the code:
public class SaveImageTask extends AsyncTask<byte[], String, File> {
private static final String IMAGE_DATA_PATH =
Environment.getExternalStorageDirectory().toString() + "/MyAppFolder/AppImages/";
private static final String TAG = "SaveImageTask";
private TaskCallback mTaskCallback;
private ProgressDialog mProgressDialog;
public SaveImageTask(TaskCallback taskCallback) {
mTaskCallback = taskCallback;
}
#Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog((Context) mTaskCallback);
mProgressDialog.setMessage("Saving Image...");
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(false);
mProgressDialog.show();
}
#Override
protected File doInBackground(byte[]... data) {
File imageFile = createOutputPictureFile();
if(imageFile == null) {
return null;
}
try {
Bitmap image = BitmapFactory.decodeByteArray(data[0], 0, data[0].length);
FileOutputStream out = new FileOutputStream(imageFile);
image.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
return imageFile;
}
#Override
public void onPostExecute(File imageFile) {
if(mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
if(mTaskCallback != null) {
mTaskCallback.onTaskComplete(imageFile);
}
}
private File createOutputPictureFile() {
File imageStorageDirectory = new File(IMAGE_DATA_PATH);
// If the default save directory doesn't exist, try and create it
if (!imageStorageDirectory.exists()){
if (!imageStorageDirectory.mkdirs()){
//Log.e(TAG, "Required media storage does not exist");
return null;
}
}
// Create a timestamp and use it as part of the file name
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.UK);
String timeStamp = dateFormat.format(new Date());
String fileName = "img_"+ timeStamp + ".jpg";
return new File (imageStorageDirectory, fileName);
}
}
The onTaskComplete(File file) looks like this:
#Override
public void onTaskComplete(File file) {
if(file == null) {
Util.showToast(this, "Save Failed.", Toast.LENGTH_SHORT);
return;
}
notifyDeviceOfNewFile(file);
ProcessImageTask pit = new ProcessImageTask(this);
pit.execute(file);
}
And the error logged is:
E/SaveImageTask: println needs a message
As it says in the title, no exception is thrown and the code actually does what it is supposed to do. I've narrowed the issue down to this line of code in the callback:
pit.execute(file);
If I comment out this line the error doesn't appear. I'm a bit stumped on what's going on. If I remove all logging in my SaveImageTask it still appears so something else is logging it.
There is a reason why a exception is not thrown, because you catch it. That is the whole concept about try-catch.
try {
Bitmap image = BitmapFactory.decodeByteArray(data[0], 0, data[0].length);
FileOutputStream out = new FileOutputStream(imageFile);
image.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
// normally you do stuff here when it fails.
}
I believe "println needs a message" is what's shown if you pass a null to Log.x(). You're probably getting an IOException - e.printstacktrace() will probably give you a better idea of why.
Ok, turns out I was being an idiot and the error was from another class which for some reason had the same TAG it was logging with. Thank you for your input and suggestions, an important lesson about copy/pasting code was learned today.
Many other methods in web service are used throughout the app and work just fine. Only one method in particular is giving trouble. When the method is invoked directly from browser, result is pretty instantaneous, so the web service itself seems to be fine. But when a breakpoint is placed on the line where the service is consumed for this particular method, everything freezes for about 1.5 to 2 minutes. Switched to async version of method and stopped the app from freezing, but the results of the method are not in the SQL database for the same 1.5 to 2 minutes. This method worked just fine up until a couple days ago and then suddenly started behaving this way.
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == Result.Ok && requestCode == 0)
{
var settings = PreferenceManager.GetDefaultSharedPreferences(this);
var assetID = settings.GetString("unit", null);
var sender = settings.GetString("sender", null);
var conn = inst2.conn();
var c = conn.CreateCommand();
c.CommandText = "Select Contract From CurrentContract";
var transID = 0;
try
{
conn.Open();
SqliteDataReader dr = c.ExecuteReader();
if (dr.HasRows)
{
dr.Read();
transID = Convert.ToInt32(dr[0].ToString());
}
dr.Close();
conn.Close();
}
catch (System.Exception ex)
{
conn.Close();
Dialog d = inst2.showBuilder(this, "Error", ex.Message);
d.Show();
}
TableLayout tl = (TableLayout)FindViewById(Resource.Id.myEquip);
View v1 = tl.FindViewWithTag(sender);
Button bt = (Button)v1;
Bitmap bitmap = (Android.Graphics.Bitmap)data.Extras.Get("data");
string base64String = "";
using (var stream = new System.IO.MemoryStream())
{
bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Png, 0, stream);
byte[] imageBytes = stream.ToArray();
base64String = Convert.ToBase64String(imageBytes);
}
try
{
//This is where it gets stuck
inst.saveImage(base64String, assetID, transID);
bt.Visibility = ViewStates.Invisible;
conn.Open();
c.CommandText = "Drop Table If Exists CurrentContract";
c.ExecuteNonQuery();
conn.Close();
}
catch (System.Exception ex)
{
conn.Close();
Dialog d = inst2.showBuilder(this, "Error", ex.Message);
d.Show();
}
}
}
inst.saveImage is a void that runs a SQL stored procedure that simply updates the line of the contract with the base64 encoded string containing values from the camera intent.
[WebMethod]
public void saveImage(string stream, string assetID, int transID)
{
var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["conn"].ToString());
var comm = new SqlCommand("saveContractImage", conn);
comm.CommandType = System.Data.CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#AssetID", assetID);
comm.Parameters.AddWithValue("#TransID", transID);
comm.Parameters.AddWithValue("#Bytes", stream);
try
{
conn.Open();
comm.ExecuteNonQuery();
conn.Close();
}
catch (Exception ex)
{
conn.Close();
throw (ex);
}
}
I think this may have been device specific. Have used on other devices since and seems to work ok.