Backwards-compatible BackupAgent - android

I am looking into using the new Backup API that available since Android 2.2, but need to maintain backwards compatibility (to 1.5 to be exact).
The docs state:
The backup service and the APIs you must use are available only on devices running API Level 8 (Android 2.2) or greater, so you should also set your android:minSdkVersion attribute to "8". However, if you implement proper backward compatibility in your application, you can support this feature for devices running API Level 8 or greater, while remaining compatible with older devices.
I indeed build against the level 8 targetSdkVersion with level 3 minSdkVersion and try to use a wrapper class (with reflection) to overcome the problem that the application will not run if you implement a class that extends an nonexisting class.
Here is the problem: since we don't make actual calls to the BackupHelper class ourselves, we can't check upfront if the class indeed exists. (As is explained in the Android Backwards Compatibility documentation with a checkAvailable() method.) The class will therefore be instantiated and cast to a BackupAgent. But since we use reflection, it doesn't actually override BackupAgent and an exception occurs at runtime when the backup is requested:
java.lang.RuntimeException: Unable to create BackupAgent org.transdroid.service.BackupAgent: java.lang.ClassCastException: org.transdroid.service.BackupAgent
Here is my approach to a backwards compatible BackupAgent: http://code.google.com/p/transdroid/source/browse/#svn/trunk/src/org/transdroid/service where the BackupAgent.java is the 'regular' BackupAgentHelper-extending class and BackupAgentHelperWrapper is the reflection-based wrapper class.
Anyone successfull in implementing a BackupAgent with backwards compatibility?

As an alternative, you can just use pure reflection to talk to the BackupManager:
public void scheduleBackup() {
Log.d(TAG, "Scheduling backup");
try {
Class managerClass = Class.forName("android.app.backup.BackupManager");
Constructor managerConstructor = managerClass.getConstructor(Context.class);
Object manager = managerConstructor.newInstance(context);
Method m = managerClass.getMethod("dataChanged");
m.invoke(manager);
Log.d(TAG, "Backup requested");
} catch(ClassNotFoundException e) {
Log.d(TAG, "No backup manager found");
} catch(Throwable t) {
Log.d(TAG, "Scheduling backup failed " + t);
t.printStackTrace();
}
}
Point the android:backupAgent straight at a v2.2 class; it will never be loaded on a pre-v2.2 VM, so there won't be any linkage problems.

I don't see why you run into this problem.
I have the same issue: I want to support backup with a app that supports also 1.5 (API 3).
There is no problem in creating my BackupAgentHelper class, since that class is never called from my own code, but from the BackupManager i.e. the system itself. Therefore I don't need to wrap it, and I don't see why you should be doing that:
public class MyBackupAgentHelper extends BackupAgentHelper {
#override onCreate()
{
\\do something usefull
}
However, you do want to get a backup running, to do that you need to call on BackupManager.dataChanged() whenever your data changes and you want to inform the system to backup it (using your BackupAgent or BackupAgentHelper).
You do need to wrap that class, since you call it from you application code.
public class WrapBackupManager {
private BackupManager wrappedInstance;
static
{
try
{
Class.forName("android.app.backup.BackupManager");
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
public static void checkAvailable() {}
public void dataChanged()
{
wrappedInstance.dataChanged();
}
public WrapBackupManager(Context context)
{
wrappedInstance = new BackupManager(context);
}
}
You then call it from your code when you change a preference or save some data.
Some code from my app:
private static Boolean backupManagerAvailable = null;
private static void postCommitAction() {
if (backupManagerAvailable == null) {
try {
WrapBackupManager.checkAvailable();
backupManagerAvailable = true;
} catch (Throwable t) {
backupManagerAvailable = false;
}
}
if (backupManagerAvailable == true) {
Log.d("Fretter", "Backup Manager available, using it now.");
WrapBackupManager wrapBackupManager = new WrapBackupManager(
FretterApplication.getApplication());
wrapBackupManager.dataChanged();
} else {
Log.d("Fretter", "Backup Manager not available, not using it now.");
}
So, hopefully this works for you!
(If you call adb shell bmgr run every time you want to emulate the actual system initiated backupprocess it should properly backup and restore when you reinstall the app.)

You need to set the minSDK version to the following:
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8"/>
and setting the build target to sdk 8 (project properties in eclipse '.default.properties'):
# Project target.
target=android-8
Now to call new stuff added in SDK 8 you have to use reflection: http://developer.android.com/resources/articles/backward-compatibility.html

I ran into the same problem and here's what I did to work it out.
You don't extend BackupAgent with the wrapper, you extend it with the wrapped class. So you make your real backup class:
public class MyBackup extends BackupAgent {
#Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
// TODO Auto-generated method stub
}
#Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// TODO Auto-generated method stub
}
Okay, and then you make a wrapper like the android developer backwards compatibility article said to do. Note that this class does not extend BackupAgent:
public class WrapMyBackup {
private MyBackup wb;
static {
try {
Class.forName("MyBackup");
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/** call this wrapped in a try/catch to see if we can instantiate **/
public static void checkAvailable() {}
public WrapMyBackup() {
wb = new MyBackup();
}
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
wb.onBackup(oldState, data, newState);
}
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
wb.onRestore(data, appVersionCode, newState);
}
public void onCreate() {
wb.onCreate();
}
public void onDestroy() {
wb.onDestroy();
}
}
Finally, in your manifest, you declare the wrapper as your backup agent:
<application
android:label="#string/app_name"
android:icon="#drawable/ic_launch_scale"
android:backupAgent="WrapMyBackup"
>
Since your wrapper has the proper methods defined you won't run into a problem when the backup manager casts it to a BackupAgent. Since lower API levels won't have a BackupManager the code will never get called, so you won't run into any runtime exceptions there either.

How about
if (android.os.Build.VERSION.SDK_INT >= 8)
{
BackupManager bm = new BackupManager(this);
bm.dataChanged();
}

Insted of just calling BackupManager.dataChanged, check if the class exists first.
try {
Class.forName("android.app.backup.BackupManager");
BackupManager.dataChanged(context.getPackageName());
} catch (ClassNotFoundException e) {
}

Related

How to WifiP2pDevice.deviceName for current device?

I know similar questions are asked but the answers didn't work for me. I tried this answer but it throws null pointer exception. I also saw this answer but WifiP2pManager does not have any property or method that returns device name.
Can anyone please help?
I basically want to show user their device name and let them change it if they want to set custom name.
Thanks.
If you're still looking for the answer, here's how:
Identify own device name
This becomes available upon receiving the WIFI_P2P_THIS_DEVICE_CHANGED_ACTION intent in your broadcast receiver. Simply use the deviceName member variable like so:
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
WifiP2pDevice self = (WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
// Now self.deviceName gives you own device name
} else if(WifiP2pManager.WIFI_P2P...) {
...
2. Change own device name
There's no method to change the device name using the WifiP2pManager as per the develper docs, and although a public setDeviceName() method exists in the source code, you can't call it on your object (probably to keep devs from calling it on an object representing a nearby peer device). What worked for me was to obtain a Method object representing said method and invoking it on my WifiP2pManager instance:
private WifiP2pManager manager;
private WifiP2pManager.Channel channel;
...
public void changeDeviceName(String deviceNewName) {
try {
Method method = manager.getClass().getMethod("setDeviceName", WifiP2pManager.Channel.class, String.class, WifiP2pManager.ActionListener.class);
method.invoke(manager, channel, deviceNewName, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Toast.makeText(MainActivity.this, "Name successfully changed", Toast.LENGTH_LONG).show();
}
#Override
public void onFailure(int reason) {
Toast.makeText(MainActivity.this, "Request failed: " + reason, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Name change failed: " + reason);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
Alternatively, users can rename their device manually from the advanced WiFi settings (Preferences > Advanced > WiFi Direct > Configure Device),
EDIT: Starting with Pie, use of non-SDK interfaces (essentially classes, variables or methods marked with #hide, which you access using reflection) is being restricted and will eventually be disallowed. The above method is currently greylisted (which means support for reflecting it might be removed in the future). Read up more here: https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces

Interprocess or adb communication with AndroidJUnit

I want to know, does any way exist to communicate with system during instrumentation test execution.
For example:
I have a phone with IR port on onboard & I can work with it through private SDK, also I can tune it with my application. In my Instrumentation test cases I want test app behavior based on external events which I want to configure before test separate test execution.
It's looks like
#Test
public void test() throws Exception {
setupExternalCondition(condition1_ON); // setup external transiver
assertNotNull(IR.read());
assertTrue(assertIR.write());
setupExternalCondition(condition1_OFF);
assertNotNull(IR.read());
assertFalse(IR.write());
}
It's very simple example but there is a lot of "conditions", and sdk updating frequencies to high. I can't do all of this verification manually, and can't ask "transiver&SDK team" make a mock states list for writing just a unit test for coverage. So I want somehow inject external component execution to TestRuner for receiving events(or testName before test case execution) on local machine(or CI machine) to setup external condition.
Simple solution(I think) to run a tcp server on appUnderTest and request external condition change - I am not sure does it possible, and not sure about stable connection(wifi), so may be it's possible to do over adb.
Any suggestions?
P.S: test device has root permissions.
So, find not bad but not ideal solution.
Still wait for better proposition, if not may be this answer will be helpful for someone;
For "building the bridge" between local machine and AndroidJUnitTest I add next class to tests:
class IPCServiceBridge extends BroadcastReceiver {
private static final String FILTER_ID = "IPC_SERVICE";
private static IPCServiceBridge sInstance;
private boolean mIsPermitted;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("ipc.service.action")) {
mIsPermitted = true;
}
}
public static IPCServiceBridge getInstance() {
if (sInstance == null) {
sInstance = new IPCServiceBridge();
IntentFilter filter = new IntentFilter();
filter.addAction("ipc.service.action");
Context context = InstrumentationRegistry.getContext();
context.registerReceiver(sInstance, filter);
}
return sInstance;
}
public void sendIpcCommand(String commandName) {
try {
int i = 30;
mIsPermitted = false;
while (i > 0) {
pub("request:" + commandName);
Thread.sleep(1000);
if (mIsPermitted) {
break;
}
i--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!mIsPermitted) {
throw new RuntimeException("IPC service does not respond");
}
}
private static void pub(String msg) {
Log.e(FILTER_ID, msg);
}
}
Than I start adb logcat -s "filter_name", parse and check which condition should be applied for InsttUnit test. When conditions is ready i send back broadcast receiver with required action.
#Test
public void test2() throws Exception {
IPCServiceBridge.getInstance().sendIpcCommand("CONDITION#123");
}
Work good, but I'm not sure that it will be super stable.

BackupAgent subclass used for automatic restore but not manual restore

I've set up the Android Backup Service in my app using a custom class that extends BackupAgentHelper ... it basically looks like this:
public class MyBackups extends BackupAgentHelper {
#Override
public void onCreate() {
Log.d("MyBackups", "creating backup class");
this.addDefaultHelper();
String defaultSharedPrefsName = this.getPackageName() + "_preferences";
SharedPreferencesBackupHelper defaultPrefsHelper = new SharedPreferencesBackupHelper(this, defaultSharedPrefsName);
this.addHelper("default_prefs", defaultPrefsHelper);
}
#Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException {
Log.d("MyBackups", "backing up " + data);
super.onBackup(oldState, data, newState);
}
#Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException {
Log.d("MyBackups", "restoring");
super.onRestore(data, appVersionCode, newState);
// post-processing code goes here
}
}
I have this registered in the manifest file, and if I delete and reinstall the app, it runs as expected, with all the log messages appearing.
However, if I manually request a restore, like this...
BackupManager backupManager = new BackupManager(getApplicationContext());
int error = backupManager.requestRestore(
new RestoreObserver() {
public void restoreStarting(int numPackages) {
Log.d("MyBackups", "restoreStarting");
}
public void restoreFinished(int error) {
Log.d("MyBackups", "restoreFinished");
}
public void onUpdate(int nowBeingRestored, String currentPackage) {
Log.d("MyBackups", "onUpdate");
}
}
);
Log.d("MyBackups", "requestRestore result: " + error);
...restoreStarting and restoreFinished are called, and the error result is 0, but none of the BackupAgentHelper methods are called -- the "creating backup class" and "restoring" logs don't appear, and my post-processing code doesn't run. It seems as if a manual requestRestore bypasses my custom BackupAgentHelper subclass.
Is there anything else I need to hook up to make a manual restore work the same way as an automatic restore? Have you tried this and is it working for you?
This is an old question, but I was able to do just this today. I hope it helps someone having the same issue.
You need to call BackupManager dataChanged() to send a backup request.
Then, to test it and kick start the backup, you need to run
adb shell bmgr run
This will call OnCreate, OnBackup.
Then after you run backupManager.requestRestore, your OnCreate, OnRestore methods will be called.
Check out the sample here, it does exactly this (when you click the Restore button):
https://android.googlesource.com/platform/development/+/0b3758ea4e53f9bfd0b112eaa4a7dd7b7f4040f5/samples/BackupRestore?autodive=0%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F
See the FileHelperExampleAgent.java agent.

Android: bindService always returning false (Expansion APK api)

I'm trying to use the APK Expansion extension from Google to download expansion files I have hosted with them. I'm also using the code from the SampleDownloadActivity to do this, albeit slightly modified to fit in my app.
My problem is that the download is never initiated. In my class that implements IDownloadClient, onStart() is called, but onServiceConnected() is not.
I have traced this down to this line in DownloaderClientMarshaller:
if( c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
This always returns false, and therefore the service is not bound.
I'm using the calling activity within a TabHost, which has caused problems for other people. They were saying that you must not pass the TabHost context, rather that the Application context to the connect function. I've changed this by doing:
mDownloaderClientStub.connect(getApplicationContext());
instead of:
mDownloaderClientStub.connect(this);
but it doesn't help, I still get false. I'm doing all my testing on the Emulator if that makes a difference.
I'm really pulling my hair out on this one. If anyone has any ideas, I'd be extremely grateful!
In most cases, bindService() method returns false if the service was not declared in the application's Manifest file.
In my case, the problem was that I had given the wrong class object to the DownloaderClientMarshaller.CreateStub() method. I accidentally used DownloaderService.class instead of MyDownloaderService.class.
When using the downloader API, make sure to pass the correct class object that extends the base DownloaderService.
I recommend using the updated Downloader Library included in Better APK Expansion package. It has this and other issues fixed and also provides simplified API that minimizes chances to shoot yourself in the foot.
To receive the download progress, you will just have to extend the BroadcastDownloaderClient.
public class SampleDownloaderActivity extends AppCompatActivity {
private final DownloaderClient mClient = new DownloaderClient(this);
// ...
#Override
protected void onStart() {
super.onStart();
mClient.register(this);
}
#Override
protected void onStop() {
mClient.unregister(this);
super.onStop();
}
// ...
class DownloaderClient extends BroadcastDownloaderClient {
#Override
public void onDownloadStateChanged(int newState) {
if (newState == STATE_COMPLETED) {
// downloaded successfully...
} else if (newState >= 15) {
// failed
int message = Helpers.getDownloaderStringResourceIDFromState(newState);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
#Override
public void onDownloadProgress(DownloadProgressInfo progress) {
if (progress.mOverallTotal > 0) {
// receive the download progress
// you can then display the progress in your activity
String progress = Helpers.getDownloadProgressPercent(
progress.mOverallProgress, progress.mOverallTotal);
Log.i("SampleDownloaderActivity", "downloading progress: " + progress);
}
}
}
}
Check the full documentation on the library's page.

Android speech - how can you read text in Android?

How can you read data, i.e. convert simple text strings to voice (speech) in Android?
Is there an API where I can do something like this:
TextToVoice speaker = new TextToVoice();
speaker.Speak("Hello World");
Using the TTS is a little bit more complicated than you expect, but it's easy to write a wrapper that gives you the API you desire.
There are a number of issues you must overcome to get it work nicely.
They are:
Always set the UtteranceId (or else
OnUtteranceCompleted will not be
called)
setting OnUtteranceCompleted
listener (only after the speech
system is properly initialized)
public class TextSpeakerDemo implements OnInitListener
{
private TextToSpeech tts;
private Activity activity;
private static HashMap DUMMY_PARAMS = new HashMap();
static
{
DUMMY_PARAMS.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "theUtId");
}
private ReentrantLock waitForInitLock = new ReentrantLock();
public TextSpeakerDemo(Activity parentActivity)
{
activity = parentActivity;
tts = new TextToSpeech(activity, this);
//don't do speak until initing
waitForInitLock.lock();
}
public void onInit(int version)
{ //unlock it so that speech will happen
waitForInitLock.unlock();
}
public void say(WhatToSay say)
{
say(say.toString());
}
public void say(String say)
{
tts.speak(say, TextToSpeech.QUEUE_FLUSH, null);
}
public void say(String say, OnUtteranceCompletedListener whenTextDone)
{
if (waitForInitLock.isLocked())
{
try
{
waitForInitLock.tryLock(180, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
Log.e("speaker", "interruped");
}
//unlock it here so that it is never locked again
waitForInitLock.unlock();
}
int result = tts.setOnUtteranceCompletedListener(whenTextDone);
if (result == TextToSpeech.ERROR)
{
Log.e("speaker", "failed to add utterance listener");
}
//note: here pass in the dummy params so onUtteranceCompleted gets called
tts.speak(say, TextToSpeech.QUEUE_FLUSH, DUMMY_PARAMS);
}
/**
* make sure to call this at the end
*/
public void done()
{
tts.shutdown();
}
}
Here you go . A tutorial on using the library The big downside is that it requires an SD card to store the voices.
A good working example of tts usage can be found in the "Pro Android 2 book". Have a look at their source code for chapter 15.
There are third-party text-to-speech engines. Rumor has it that Donut contains a text-to-speech engine, suggesting it will be available in future versions of Android. Beyond that, though, there is nothing built into Android for text-to-speech.
Donut has this: see the android.speech.tts package.

Categories

Resources