Can't use backup with Android Backup Service - java

I'm trying to integrate the Backup Service with my app. It's all working right up to the moment I make the call to:
BackupManager bm = new BackupManager(context);
bm.dataChanged();
I see the following in my Logcat:
W/BackupManagerServiceļ¹• dataChanged but no participant pkg='com.company.app' uid=10102
Here's my implementation of BackupAgentHelper https://gist.github.com/cloakedninjas/fe135cb04bf324e26b0c if it helps...
public class CordovaBackupAgentHelper extends BackupAgentHelper {
static final String FILE_NAME = "gameData.json";
static final String FILES_BACKUP_KEY = "data_file";
#Override
public void onCreate() {
FileBackupHelper helper = new FileBackupHelper(this, FILE_NAME);
addHelper(FILES_BACKUP_KEY, helper);
}
#Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
Log.d(Backup.LOG_TAG, "Backup requested: " + oldState.toString() + " | " + newState.toString());
synchronized (Backup.sDataLock) {
Log.d(Backup.LOG_TAG, "Backup requested: " + data.toString());
super.onBackup(oldState, data, newState);
}
}
#Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
synchronized (Backup.sDataLock) {
Log.d(Backup.LOG_TAG, "Restore given: " + data.toString());
super.onRestore(data, appVersionCode, newState);
}
}
}
I never see my log entry Backup requested. I've checked my Android manifest has the following attribute android:backupAgent="CordovaBackupAgentHelper"
Can anyone shed any light on this?
Update
I've noticed if I change my Manifest to have android:backupAgent="FooBar" I see no errors about undeclared class FooBar, but still see dataChanged but no participant pkg when attempting a backup. So my guess is the error is related to not being able to find my backup agent.

A couple of suggestions:
In your manifest, I think the declaration of the backup agent should either use the full name or the "dot" short form:
android:backupAgent="com.example.project.CordovaBackupAgentHelper"
or
android:backupAgent=".CordovaBackupAgentHelper"
Also, you must have backup enabled in your manifest:
android:allowBackup="true"
Having said that, I doubt these changes with make the warning message go away. I have a backup agent, and am unable to create the warning message you see by changing these declaration to invalid values.
If you are using the Google backup transport, have you registered for the service as described in the Data Backup Guide and added the key to your manifest?
Have you tried to trigger a backup operation using the adb bmgr tool to see if your agent gets called despite the warning message?

So my issue was not putting the attributes onto the correct element :(
I had:
<manifest android:backupAgent="com.cloakedninjas.cordova.plugins.BackupAgentHelper" ...
Instead of:
<application android:backupAgent="com.cloakedninjas.cordova.plugins.BackupAgentHelper" ...

Related

imread not reading an existing image on Java Android from Linux

package com.example.cppinandroid;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import org.opencv.core.Mat;
import java.io.File;
import static org.opencv.imgcodecs.Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE;
import org.opencv.imgcodecs.Imgcodecs;
public class MainActivity extends AppCompatActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById( R.id.sample_text );
Mat image;
image = Imgcodecs.imread( "/home/<myName>/a.png", CV_LOAD_IMAGE_GRAYSCALE);
if ( image.size().height > 0 )
{
tv.setText( "No datfa in image!");
}
else
{
tv.setText( "xxff " + image.size().height);
}
}
}
I am NOT using any drawable or external media. Image is present in home folder and can be opened by a normal opencv c++ program by giving the exact same path.
Someone here told me that native C++ NDK will not be able to read Linux paths. Alright. Here it is all Java.
When I execute this, it always goes in else statement and shows the height as 0.0.
I have removed the extra code.
What is the way to read a normal png from home folder in this program on Linux?
All Android devices or emulators don't have access to storages outside like your Linux storage partition, they have access to their internal storage or sdcard. In the case of the emulator, their internal storage is emulated using a file with a certain format that cannot be easily read. In an emulator or a device that has Developer options enabled, one could use the adb command found within the Android SDK platform-tools folder to transfer files into it as such:
adb push file.jpg /sdcard/file.jpg
After that, you'll need to change the path of the file your using in the code to match and also enable permissions to READ_EXTERNAL_STORAGE (here external means external to the application your running, but still internal to the device).
Someone here told me that native C++ NDK will not be able to read
Linux paths. Alright. Here it is all Java.
Looking at your question and the answer, for start this is the same problem, trying to access a file that is not part of the device/emulator internal storage. However the answer isn't entirely true, the C/C++ code can access files and directories of the internal storage as long as permission is granted to the application. I would suggest you first try fixing the problem using Java and then switch back to the code you had in your other question but with the corrected path. With Java, you'll be using the Java wrapper for the OpenCV APIs, hence you'll need to call OpenCVLoader.initDebug() to load the wrapper lib. When using pure NDK, you'll only need to load the compiled lib (System.loadLibrary(<libname>) you created with the native C/C++ code.
When you use Imgcodecs.imread(...), it will read the path on your machine, which running your application.
So, if you run the Java Application, it will run on your JVM within your computer, that mean it read the path like ~/home/... on your computer and that path exist, so it can get somethings.
But, Android App will run on DVM within Android Device, that mean when you read ~/home/.., it will take that path on Android Device, but it wasn't exist on Android Devices. So you can't get anything.
The best practice, you should use Imgcodecs.imread(...) with the External Storage Path like some guys suggest you above.
Sometime, you maybe can use Imgcodecs.imread(...) on /mtn/..,path of SD Card, but it isn't correct completely.
see this example code, maybe help you, i tested this code and work for me, and i can get width and height of any image.
1) first you need import OpenCVLibrary to your project: see this Link - Link
2) then you need set READ_EXTERNAL_STORAGE permision to your application:
plz add this command on your AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3) you need file picker for get specific file i use ru.bartwell:exfilepicker: Link
implementation 'ru.bartwell:exfilepicker:2.1'
4) and at the end you just add this simple code to your MainActivity:
private static final int READ_STORAGE_PERMISSION_REQUEST_CODE = 1;
private static final int EX_FILE_PICKER_RESULT = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initLoadOpenCV();
if (!checkPermissionForReadExtertalStorage()) {
requestPermissionForReadExtertalStorage();
}
}
private void initLoadOpenCV() {
boolean isDebug = OpenCVLoader.initDebug();
if (isDebug) {
Log.i("init Opencv", "init openCV success!!");
} else {
Log.e("init Opencv", "init openCV failure!!");
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == EX_FILE_PICKER_RESULT) {
ExFilePickerResult result = ExFilePickerResult.getFromIntent(data);
if (result != null && result.getCount() > 0) {
// Here is object contains selected files names and path
Log.i("folderLocation", result.getPath() + result.getNames().get(0));
Mat srcMat1 = Imgcodecs.imread(result.getPath() + result.getNames().get(0));
if (srcMat1.empty()) {
return;
}
int width = srcMat1.width();
int height = srcMat1.height();
int type = srcMat1.type();
Log.i("width", srcMat1.width() + "");
Log.i("height", srcMat1.height() + "");
Log.i("type", srcMat1.type() + "");
}
}
}
public boolean checkPermissionForReadExtertalStorage() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int result = getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED;
}
return false;
}
public void requestPermissionForReadExtertalStorage() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
READ_STORAGE_PERMISSION_REQUEST_CODE);
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case READ_STORAGE_PERMISSION_REQUEST_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.e("value", "Permission Granted, Now you can use local drive .");
ExFilePicker exFilePicker = new ExFilePicker();
exFilePicker.start(this, EX_FILE_PICKER_RESULT);
} else {
Log.e("value", "Permission Denied, You cannot use local drive .");
requestPermissionForReadExtertalStorage();
}
break;
}
}
The first problem mentioned in the comment below the question is that you must load the native library that implements the image loading. This can be done with the following code:
static {
// TODO: use OpenCVLoader.initAsync for a real application
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Failed to initialize OpenCV.");
}
}
In a real application you would use the initAsync function such that the loading of the libraries does not block the main thread. In a simple example this does not matter.
Another problem is, that file IO on Android requires a permission if you want to access files in arbitrary directories. The permission must be declared in your manifest file. This can be done by adding the following two lines above the application tag in the manifest file.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
These permissions must be requested at runtime. To check if the permission was granted already the following code can be used:
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// do something with the permission
}
If the permission is not available, it can be requested as follows:
ActivityCompat.requestPermissions(this, new String[]{
android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, RW_PERMISSION_REQUEST_CODE);
Note: we only request the write permission because they are grouped and if the user grants the write permission we automatically also obtain the read permission.
To handle the result the onRequestPermissionsResult callback in the activity class should be overwritten as seen in the full code example below. Because the permission system is quite complex take a look at the official documentation. For info on requesting permissions look here.
Finally to make the loading work the file path must be correct. The user-accessible memory locations depend on the phone manufacturer, therefore it is good to use the system methods provided by Android to find the correct path, for example getExternalStorageDirectory(). More information on the various storage locations can be found here.
Here the full code:
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.os.Environment.getExternalStorageDirectory;
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
private static final int RW_PERMISSION_REQUEST_CODE = 123;
static {
// TODO: use OpenCVLoader.initAsync for a real application
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Failed to initialize OpenCV.");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(
this, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissionDenied();
} else {
permissionGranted();
}
}
private void permissionDenied() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, WRITE_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(this)
.setTitle("Read/Write permission required to read an image file.")
.setCancelable(false)
.setPositiveButton("Grant", (dialog, which) ->
ActivityCompat.requestPermissions(this, new String[]{
WRITE_EXTERNAL_STORAGE}, RW_PERMISSION_REQUEST_CODE))
.setNegativeButton("Deny", (dialog, which) -> {
Toast.makeText(this,
"App cannot work without permission.", Toast.LENGTH_LONG).show();
this.finish();
})
.create()
.show();
} else {
ActivityCompat.requestPermissions(
this, new String[]{WRITE_EXTERNAL_STORAGE}, RW_PERMISSION_REQUEST_CODE);
}
}
private void permissionGranted() {
String path = getExternalStorageDirectory().getAbsolutePath() + "/a.png";
Mat image = Imgcodecs.imread(path, Imgcodecs.IMREAD_GRAYSCALE);
if (image.empty()) {
Toast.makeText(this, "Failed image", Toast.LENGTH_LONG).show();
} else {
Size size = image.size();
Toast.makeText(this, "Loaded image " + size.height, Toast.LENGTH_LONG).show();
// the following code is only necessary to display the image in an ImageView
ImageView iv = findViewById(R.id.imageView);
Mat tmp = new Mat((int) size.height, (int) size.width, CvType.CV_8U, new Scalar(4));
try {
Imgproc.cvtColor(image, tmp, Imgproc.COLOR_GRAY2RGBA, 4);
Bitmap bmp = Bitmap.createBitmap(tmp.cols(), tmp.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(tmp, bmp);
iv.setImageBitmap(bmp);
} catch (CvException e) {
Log.d(TAG, e.getMessage());
Toast.makeText(this, "Couldn't convert image.", Toast.LENGTH_LONG).show();
}
}
}
#Override
public void onRequestPermissionsResult(
int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if (requestCode == RW_PERMISSION_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
permissionGranted();
} else {
permissionDenied();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
To make this code work add an ImageView with id imageView to your activity_main.xml layout file.
Before you read on further I would like to clarify a few things
From question it is not clear where the code is running? It seems that the user is running this android app on a virtual machine or emulator.
1.1 - From the question it seems that the file she wants to open is on the home directory of Linux machine and not in the emulator's storage or on an Android device - in this case Please note that android apps running on emulator can not access files from your computer.
---- so if you were trying to access file on your Linux pc from within android emulator or vm, please note that it is not possible. Instead copy and put the file in the android emulator or device on which your app will be running.
Please clarify question and let us know whether you have file on the emulator storage (or android device) or it is on your pc and code running on emulator.
If you have file on emulator or android device, please make sure you have right permissions declared in manifest and you have also requested user for permission to read storage before trying to read the image.
Update
Thank you for the response in comments.
How to put file on emulator?
To add a file to the emulated device, drag the file onto the emulator screen. The file is placed in the /sdcard/Download/ directory. You can view the file from Android Studio using the Device File Explorer, or find it from the device using the Downloads or Files app, depending on the device version
source https://developer.android.com/studio/run/emulator
For permissions related stuff you can refer easy to follow documentation on official website - https://developer.android.com/training/permissions/requesting
or check out this question - Android marshmallow request permission?
You can also check -- https://stackoverflow.com/a/30434631/9640177
Depreciation note
Please check https://stackoverflow.com/a/59812593/9640177
To avoid dealing with permissions and external directories, you can also transfer your file to your app's internal storage using android studio -- explore device storage.
Update 2
Refer this answer please - https://stackoverflow.com/a/5990806/9640177
If you want to access them in runtime and not push the files there you have to have a public IP on the internet in which you can access and expose the files on HTTP with something like Apache Tomcat or FTP and then access that from the emulator.
You can also access it locally without exposing it to the whole internet, by accessing from the Android emulator to the "localhost" IP. You'll have to search which IP this is.
So your use case necessitates accessing files from location on your pc, you can use something like tomcat to create a local http server.

ApiException in android with status code 7 while using safetynet attestation API

Apiexception with status code 7 occurs due to network error but that doesn't seem to be the issue since internet is working fine on the device.
Code snippet-
SafetyNet.getClient(this).attest(nonce, API_KEY)
.addOnSuccessListener(this,
new OnSuccessListener<SafetyNetApi.AttestationResponse>() {
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onSuccess(SafetyNetApi.AttestationResponse response) {
mResult = response.getJwsResult();
Log.i(TAG, "safetynet result is" + mResult + "\n");
mainres = initialDataExtraction(response.getJwsResult());
Log.i(TAG, "Main result is" + mainres + "\n");
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
if (e instanceof ApiException) {
ApiException apiException = (ApiException) e;
int statuscode = apiException.getStatusCode();
Log.i(TAG,"EXCEPTION CODE: "+statuscode);
} else {
Log.d(TAG, "Error: " + e.getMessage());
}
}
});
The issue was with my API key. I was using the API key of a colleague which was restricted. Using my own API key it worked fine.
My issue was i registered SHA1 from play console but not SHA1 in my android studio.
We have this error when the Safetynet API quotas are exceeded. The default quota is 10_000 requests per day, reset at midnight.
Downgrading the com.google.android.gms:play-services-safetynet dependency on the app's build.gradle from 18.0.1 to 18.0.0 fixed it for me.
Do still double-check your API key restrictions (SHA1, package name) since they might cause this error too if there's something wrong with them, but downgrading the API a notch fixed it for me.
Also, double-check if you're reading the APK key from your keys file correctly, try hard-coding the API key into the class as a String and see if it works, of course don't do this in production code, you must hide your API key in release.

Sending email with ACRA on Android

I am trying to send a crash report from an android application and it is opening a email client and entering com.x.x Crash Report in the email body. I cant seem to be able to get any other information in the email. The docs dont have a complete email example and i have reportContent added (but APP_VERSION is not resolving). I have also updated my manifest file with READ_PHONE_STATE
I'm referencing this https://github.com/ACRA/acra/wiki/AdvancedUsage#choosing-which-fields-to-be-included-in-reports.
Any help would be appreciated
Manifest
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Application
#AcraCore(
reportContent = { /*APP_VERSION,*/ ANDROID_VERSION, PHONE_MODEL, CUSTOM_DATA, STACK_TRACE, LOGCAT}
)
#AcraMailSender(mailTo = "X#protonmail.com")
public class MyApplication extends Application {
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
ACRA.init(this);
}
I know this is dead post, nevertheless there's mistake in ACRA's site, example should be reading:
#AcraCore(
buildConfigClass = org.acra.BuildConfig.class,
logcatArguments = {"-t", "200", "-v", "time"},
reportFormat= StringFormat.JSON,
reportContent = {
ReportField.USER_COMMENT,
ReportField.APP_VERSION_NAME,
ReportField.APP_VERSION_CODE,
ReportField.ANDROID_VERSION,
ReportField.PHONE_MODEL,
ReportField.CUSTOM_DATA,
ReportField.STACK_TRACE,
ReportField.LOGCAT }
)
#AcraMailSender(
mailTo = "email#domain.com"
)
I used Crashlytics instead as suggested by #gabe-sechan

Trigger MediaScanner without an entire class [duplicate]

I got this class:
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.util.Log;
public class MediaScannerWrapper implements
MediaScannerConnection.MediaScannerConnectionClient {
private MediaScannerConnection mConnection;
private String mPath;
private String mMimeType;
// filePath - where to scan;
// mime type of media to scan i.e. "image/jpeg".
// use "*/*" for any media
public MediaScannerWrapper(Context ctx, String filePath, String mime){
mPath = "/sdcard/DCIM/Camera";
mMimeType = "jpg";
mConnection = new MediaScannerConnection(ctx, this);
}
// do the scanning
public void scan() {
mConnection.connect();
}
// start the scan when scanner is ready
public void onMediaScannerConnected() {
mConnection.scanFile(mPath, mMimeType);
Log.w("MediaScannerWrapper", "media file scanned: " + mPath);
}
public void onScanCompleted(String path, Uri uri) {
// when scan is completes, update media file tags
}
}
How to use it in the other class?
I don't know how to properly use classes, I tried but nothing is working.
I do something wrong, but I don't know what, can someone help me with this.
The Story
Before Android 4.4, we could just send a broadcast to trigger the media scanner on any particular file, or folder or even on the root of the storage. But from 4.4 KitKat, this have been fixed by the Android Developers.
Why do I say fixed? The reason is simple. Sending a broadcast using MEDIA_MOUNTED on the root directory is very expensive. Running the Media Scanner is an expensive operation and the situation gets even worse when the user has got a lot of files in the storage and deep folder structures.
Before Android 4.4
Keep it straight and simple. If you are targeting your app before Android 4.4. But keep in mind not to use it on the root directory unless absolutely necessary.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
From Android 4.4
There are two ways for you.
i) The first one is very similar to the previous example, but may not work efficiently and is not recommended too.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
ii) Now, let us move on to the most recommended and efficient solution to this problem.
Add the file paths of the files which have been updated, like this, in a String type ArrayList
ArrayList<String> toBeScanned = new ArrayList<String>();
toBeScanned.add(item.getFilePath());
Now you need to run scanFile() static method of the MediaScannerConnection class and pass the String array containing the list of all the files which have been updated and needs to be media scanned.
You can also put a listener to respond when the scanning has been finished for individual files.
String[] toBeScannedStr = new String[toBeScanned.size()];
toBeScannedStr = toBeScanned.toArray(toBeScannedStr);
MediaScannerConnection.scanFile(getActivity(), toBeScannedStr, null, new OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
System.out.println("SCAN COMPLETED: " + path);
}
});
Hey I found out how to do it with a very simple code.
Just call this line of code:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
This should trigger mediascanner.
In Android, there is a content database which is used by the media scanner to keep track of all the media content present on the device.
When Android boots up, the mediascanner service is launched and runs through the entire external storage to find if there is any new media content if it finds one then,
It adds an entry of that media content into the content database
Each entry in the content database contains metadata of the media content like Name, date, file size, type of file, etc..
So when you make a modification to a media content, you will need to update the content database also.
If the content database is not update then other applications also will not be able to access that particular media content.
Running the media scanner just updates the content database
Instead of running the media scanner, you can update the content database yourself and it should resolve the problem.
Here is an explanation on how to insert, delete, update using the content resolver. (Search for the section "Inserting, Updating, and Deleting Data")
Edit:
There is a sample code in this answer. Check for the answer by Janusz.
File file = new File(absolutePath);
Uri uri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
sendBroadcast(intent);
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
Reference: http://developer.android.com/training/camera/photobasics.html#TaskGallery
The Add the Photo to a Gallery Section
As #Aritra Roy's answer, i decide to make an experiment about this issue.
What i got here are:
Intent.ACTION_MEDIA_MOUNTED and Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
can accept individual file path, so sendBroadcast(new
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(filePath)));
or sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse(filePath))); will be valid.
If you use individual file path with Intent.ACTION_MEDIA_MOUNTED on Kitkat or above, your application will still crash
If you use Intent.ACTION_MEDIA_SCANNER_SCAN_FILE or MediaScannerConnection on device lower than Kitkat, your application will not force close, but the method will just simply not working as you want.
From that experiment, i think the best method to handle is
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
MediaScannerConnection.scanFile(context, new String[]{imagePath}, null, new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
//something that you want to do
}
});
} else {
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + imagePath)));
}
Let me know if i missed something

Android/Java -- Post simple text to Facebook wall?

I'm trying to integrate posting to one's wall from within my app. I already have an area where the user can save his/her username and password (encrypted). I would like my program to recall the saved username and password, pass that to Facebook for authentication, and then allow the app to post simple text (maybe a link too) to the user's wall.
That said, I've read everything on the developer pages at Facebook (the api looks completely foreign to me... I've never done any type of web app development before... just desktop apps), and experimented with the Java libraries here but to be honest, I don't understand any of the various implementations. Some claim to be simple to use, but apparently they are all way above my head.
I've even tried messing with the official Facebook Android SDK, but that uses a webview interface, and I can't pass in the username and password for easy authentication. Plus, I'm still clueless as to how to post to the wall even after correct authentication.
Please help.
Thanks.
Oh, btw I already have a Facebook API key and Application ID.
[UPDATE 1]
For further clarification:
If I use the following code snippet with the official Facebook Android SDK http://github.com/facebook/facebook-android-sdk what should I do next (after the user has logged-in)? This is unclear to me.
Facebook facebookClient = new Facebook();
facebookClient.authorize(this, "[APP ID]", new String[] {"publish_stream", "read_stream", "offline_access"}, this);
where "this" is an Activity that implements a DialogListener, and "[APP ID]" is my Facebook application ID.
Thanks.
[UPDATE 2]
I found a solution (see below), though the only thing missing is the ability to auto-populate the login text boxes with the data I have stored in the app. The official Facebook Android SDK may not allow for this. I'll keep looking into it.
I figured it out, with Tom's help (thanks). The key was creating a dialog with the "stream.publish" API call, using the Facebook Android SDK. Here are the steps:
Download the official Facebook Android SDK : http://github.com/facebook/facebook-android-sdk
Import the project files into Eclipse.
Export the project as a *.jar file. (this might cause a conflict)
[UPDATE]
Facebook recently updated the source code and I noticed the icon file caused resource id conflicts with my projects (Android 1.5+). My solution is to forget about exporting as a jar. Instead, copy the Facebook "com" folder directly into your app's "src" folder (i.e. "com.facebook.android" should be a package in your app... right alongside your source files). If you already have a "com" folder in your "src" folder, don't worry about any dialog boxes that appear about overwriting files, none of your source files should be overwritten. Go back into Eclipse, and refresh the "src" folder and "com.facebook.android" should now be listed as a package. Copy one of the included Facebook icons to your app's "drawable" folder and refresh that as well. Eclipse will complain about the "FbDialog.java" file... just add an import pointing to your app's "R" file to the header of that file (e.g. if your app's package name is "com.android.myapp," then add this: "import com.android.myapp.R;"). Go to #5 if you needed to do this.
Add the .jar file to your project's build path
Look at the following simplified example code:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import com.facebook.android.*;
import com.facebook.android.Facebook.DialogListener;
public class FacebookActivity extends Activity implements DialogListener,
OnClickListener
{
private Facebook facebookClient;
private LinearLayout facebookButton;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.test);//my layout xml
facebookButton = (LinearLayout)this.findViewById(R.id.Test_Facebook_Layout);
}
#Override
public void onComplete(Bundle values)
{
if (values.isEmpty())
{
//"skip" clicked ?
return;
}
// if facebookClient.authorize(...) was successful, this runs
// this also runs after successful post
// after posting, "post_id" is added to the values bundle
// I use that to differentiate between a call from
// faceBook.authorize(...) and a call from a successful post
// is there a better way of doing this?
if (!values.containsKey("post_id"))
{
try
{
Bundle parameters = new Bundle();
parameters.putString("message", "this is a test");// the message to post to the wall
facebookClient.dialog(this, "stream.publish", parameters, this);// "stream.publish" is an API call
}
catch (Exception e)
{
// TODO: handle exception
System.out.println(e.getMessage());
}
}
}
#Override
public void onError(DialogError e)
{
System.out.println("Error: " + e.getMessage());
}
#Override
public void onFacebookError(FacebookError e)
{
System.out.println("Error: " + e.getMessage());
}
#Override
public void onCancel()
{
}
#Override
public void onClick(View v)
{
if (v == facebookButton)
{
facebookClient = new Facebook();
// replace APP_API_ID with your own
facebookClient.authorize(this, APP_API_ID,
new String[] {"publish_stream", "read_stream", "offline_access"}, this);
}
}
}
AsyncFacebookRunner mAsyncRunner;
Facebook facebook =new Facebook("Your app id");
btnLogin.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
facebook.authorize(FbdemoActivity.this, new String[]{ "user_photos,publish_checkins,publish_actions,publish_stream"},new DialogListener() {
#Override
public void onComplete(Bundle values) {
}
#Override
public void onFacebookError(FacebookError error) {
}
#Override
public void onError(DialogError e) {
}
#Override
public void onCancel() {
}
});
}
});
public void postOnWall(String msg) {
Log.d("Tests", "Testing graph API wall post");
try {
String response = facebook.request("me");
Bundle parameters = new Bundle();
parameters.putString("message", msg);
parameters.putString("description", "test test test");
response = facebook.request("me/feed", parameters,
"POST");
Log.d("Tests", "got response: " + response);
if (response == null || response.equals("") ||
response.equals("false")) {
Log.v("Error", "Blank response");
}
} catch(Exception e) {
e.printStackTrace();
}
Here is an objective answer to your new question, "What do I do next?"
A quick look at the source code leads me to believe this is what you do:
Check this URL for the REST (http://en.wikipedia.org/wiki/Representational_State_Transfer) API methods you can use to leave a comment/post:
http://developers.facebook.com/docs/reference/rest/
Specifically this: http://developers.facebook.com/docs/reference/rest/links.post
Check out lines 171 through 295 of Facebook.java
http://github.com/facebook/facebook-android-sdk/blob/master/facebook/src/com/facebook/android/Facebook.java
To see how to use the API to make these requests.
You'll probably want this method (it's overloaded, see the code).
/**
* Make a request to Facebook's old (pre-graph) API with the given
* parameters. One of the parameter keys must be "method" and its value
* should be a valid REST server API method.
*
* See http://developers.facebook.com/docs/reference/rest/
*
* Note that this method blocks waiting for a network response, so do not
* call it in a UI thread.
*
* Example:
* <code>
* Bundle parameters = new Bundle();
* parameters.putString("method", "auth.expireSession");
* String response = request(parameters);
* </code>
*
* #param parameters
* Key-value pairs of parameters to the request. Refer to the
* documentation: one of the parameters must be "method".
* #throws IOException
* if a network error occurs
* #throws MalformedURLException
* if accessing an invalid endpoint
* #throws IllegalArgumentException
* if one of the parameters is not "method"
* #return JSON string representation of the response
*/
public String request(Bundle parameters)
To those who have problems, in the new facebook(); , the string is you App_id, and just delete the APP_ID in the authorized call.
Don't know why the error message is shown, but I guess that facebook updated the facebook SDK.

Categories