I am trying to implement the Content-Provider-Sync Adapter pattern as discussed at Google IO - slide 26. My content provider is working, and my sync works when I trigger it from the Dev Tools Sync Tester application, however when I call ContentResolver.requestSync(account, authority, bundle) from my ContentProvider, my sync is never triggered.
ContentResolver.requestSync(
account,
AUTHORITY,
new Bundle());
Edit -- added manifest snippet
My manifest xml contains:
<service
android:name=".sync.SyncService"
android:exported="true">
<intent-filter>
<action
android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="#xml/syncadapter" />
</service>
--Edit
My syncadapter.xml associated with my sync service contains:
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="AUTHORITY"
android:accountType="myaccounttype"
android:supportsUploading="true"
/>
Not sure what other code would be useful. The account passed to requestSync is of "myaccounttype" and the AUTHORITY passed to the call matches my syc adapter xml.
Is ContentResolver.requestSync the correct way to request a sync? It looks like the sync tester tool binds directly to the service and calls start sync, but that seems like it defeats the purpose of integrating with the sync architecture.
If that is the correct way to request a sync then why would the sync tester work, but not my call to ContentResolver.requestSync? Is there something I need to pass in the bundle?
I am testing in the emulator on devices running 2.1 and 2.2.
Calling requestSync() will only work on an {Account, ContentAuthority} pair that is known to the system. Your app needs to go through a number of steps to tell Android that you are capable of synchronizing a specific kind of content using a specific kind of account. It does this in the AndroidManifest.
1. Notify Android that your application package provides syncing
First off, in AndroidManifest.xml, you have to declare that you have a Sync Service:
<service android:name=".sync.mySyncService" android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="#xml/sync_myapp" />
</service>
The name attribute of the <service> tag is the name of your class to connect up sync... I'll talk to that in a second.
Setting exported true makes it visible to other components (needed so ContentResolver can call it).
The intent filter lets it catch an intent requesting sync. (This Intent comes from ContentResolver when you call ContentResolver.requestSync() or related scheduling methods.)
The <meta-data> tag will be discussed below.
2. Provide Android a service used to find your SyncAdapter
So the class itself... Here's an example:
public class mySyncService extends Service {
private static mySyncAdapter mSyncAdapter = null;
public SyncService() {
super();
}
#Override
public void onCreate() {
super.onCreate();
if (mSyncAdapter == null) {
mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
}
}
#Override
public IBinder onBind(Intent arg0) {
return mSyncAdapter.getSyncAdapterBinder();
}
}
Your class must extend Service or one of its subclasses, must implement public IBinder onBind(Intent), and must return a SyncAdapterBinder when that's called... You need a variable of type AbstractThreadedSyncAdapter. So as you can see, that's pretty much everything in that class. The only reason it's there is to provide a Service, that offers a standard interface for Android to query your class as to what your SyncAdapter itself is.
3. Provide a class SyncAdapter to actually perform the sync.
mySyncAdapter is where the real sync logic itself is stored. Its onPerformSync() method gets called when it's time to sync. I figure you already have this in place.
4. Establish a binding between an Account-type and a Content Authority
Looking back again at AndroidManifest, that strange <meta-data> tag in our service is the key piece that establishes the binding between a ContentAuthority and an account. It externally references another xml file (call it whatever you like, something relevant to your app.) Let's look at sync_myapp.xml:
<?xml version="1.0" encoding="utf-8" ?>
<sync-adapter
xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts"
android:accountType="com.google"
android:userVisible="true" />
Okay, so what does this do? It tells Android that the sync adapter we've defined (the class that was called out in the name element of the <service> tag that includes the <meta-data> tag that references this file...) will sync contacts using a com.google style account.
All your contentAuthority strings have to all match, and match with what you're syncing -- This should be a string you define, if you're creating your own database, or you should use some existing device strings if you're syncing known data types (like contacts or calendar events or what have you.) The above ("com.android.contacts") happens to be the ContentAuthority string for contacts type data (surprise, surprise.)
accountType also has to match one of those known account types that are already entered, or it has to match one you're creating (This involves creating a subclass of AccountAuthenticator to get auth on your server... Worth an article, itself.) Again, "com.google" is the defined string identifying... google.com style account credentials (again, this should not be a surprise.)
5. Enable Sync on a given Account / ContentAuthority pair
Finally, sync has to be enabled. You can do this in the Accounts & Sync page in the control panel by going to your app and setting the checkbox next to your app within the matching account. Alternately, you can do it in some setup code in your app:
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
For sync to occur, your account/authority pair must be enabled to sync (like above) and the overall global sync flag on the system must be set, and the device must have network connectivity.
If your account/authority sync or the global sync are disabled, calling RequestSync() does have an effect -- It sets a flag that sync has been requested, and will be performed as soon as sync is enabled.
Also, per mgv, setting ContentResolver.SYNC_EXTRAS_MANUAL to true in the extras bundle of your requestSync will ask android to force a sync even if global sync is off (be respectful of your user here!)
Finally, you can setup a periodic scheduled sync, again with ContentResolver functions.
6. Consider implications of multiple accounts
It is possible to have more than one account of the same type (two #gmail.com accounts set up on one device or two facebook accounts, or two twitter accounts, etc...) You should consider the application implications of doing that... If you have two accounts, you probably don't want to try to sync both of them into the same database tables. Maybe you need to specify that only one can be active at a time, and flush the tables and resync if you switch accounts. (through a property page that queries what accounts are present). Maybe you create a different database for each account, maybe different tables, maybe a key column in each table. All application specific and worthy of some thought. ContentResolver.setIsSyncable(Account account, String authority, int syncable) might be of interest here. setSyncAutomatically() controls whether an account/authority pair is checked or unchecked, whereas setIsSyncable() provides a way to uncheck and grey out the line so the user can't turn it on. You might set one account Syncable and the other not Syncable (dsabled).
7. Be aware of ContentResolver.notifyChange()
One tricky thing. ContentResolver.notifyChange() is a function used by ContentProviders to notify Android that the local database has been changed. This serves two functions, first, it will cause cursors following that content uri to update, and in turn requery and invalidate and redraw a ListView, etc... It's very magical, the database changes and your ListView just updates automatically. Awesome. Also, when the database changes, Android will request Sync for you, even outside your normal schedule, so that those changes get taken off the device and synced to the server as rapidly as possible. Also awesome.
There's one edge case though. If you pull from the server, and push an update into the ContentProvider, it will dutifully call notifyChange() and android will go, "Oh, database changes, better put them on the server!" (Doh!) Well-written ContentProviders will have some tests to see if the changes came from the network or from the user, and will set the boolean syncToNetwork flag false if so, to prevent this wasteful double-sync. If you're feeding data into a ContentProvider, it behooves you to figure out how to get this working -- Otherwise you'll end up always performing two syncs when only one is needed.
8. Feel happy!
Once you have all this xml metadata in place, and sync enabled, Android will know how to connect everything up for you, and sync should start working. At this point, a lot of things that are nice will just click into place and it will feel a lot like magic. Enjoy!
I was caling setIsSyncable after AccountManager setAuthToken method. But setAuthToken returned function before setIsSyncable was reached. After order changes everything worked fine!
I noticed the that the requestSync will trigger the sync implementation when there is internet connection
Related
I am using the Android AutoBackup feature in my app. These are my manifest settings.
<application
android:allowBackup="true"
android:fullBackupContent="#xml/backup_rules"
android:fullBackupOnly="true"
Is there a way to know when the last backup for my app was made?
The only thing I have found says this:
schedule
Backup documentation
Frequency Apps must issue a request when there is data that is ready to be backed up. Requests from multiple apps are batched and executed every few hours. Backups happen automatically roughly once a day.
Also, it looks like if you write your own agent you can register for backup or restore events.
events
also, one last thing, when you implement the events it looks like backup gives access to the date of the last backup. As far as I can see, a customs agent gets you the info you need with some coding
examples
UPDATE
I'm noticing that I actually am receiving the NETWORK_LOGS_AVAILABLE intent! The problem is, it's taking a very long time (over an hour?) to receive it.
Is there any known way to increase the frequency of receiving these events?
Original Question
I am trying to process DNS events that can now be read after receiving the onNetworkLogsAvailable intent in a DeviceAdminReceiver application. This functionality was made available as of Android 8.0.
For some reason, I am never receiving this intent, even though I am successfully calling the setNetworkLoggingEnabled method. Upon admin being enabled, I am receiving the ACTION_DEVICE_ADMIN_ENABLED event, but nothing else after that.
Here's where I enable network logging:
public class NetworkAdminReceiver extends DeviceAdminReceiver {
#Override
public void onEnabled(Context context, Intent intent) {
DevicePolicyManager manager =
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
if ( manager == null )
{
throw new IllegalStateException("Unable to get DevicePolicyManager");
}
if (manager.isDeviceOwnerApp(context.getPackageName())) {
manager.setNetworkLoggingEnabled(getWho(context), true);
}
else
{
Toast.makeText(context, "This application is not device owner. DNS logging only works" +
" when this application is setup as the Device Owner", Toast.LENGTH_LONG).show();
}
}
// *snip* rest of class
}
Although I am not sure whether it's required (cannot find in documentation), I've also added the NETWORK_LOGS_AVAILABLE intent action to the receiver's filter:
<receiver android:name=".admin.NetworkAdminReceiver"
android:label="#string/device_admin"
android:description="#string/device_admin_description"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data android:name="android.app.device_admin"
android:resource="#xml/device_admin" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
<action android:name="android.app.action.NETWORK_LOGS_AVAILABLE"/>
</intent-filter>
</receiver>
The application is marked as the device owner, network logging is enabled, and yet I never receive the intent. The only explanation I could think of is that network logs do not become available very frequently, but I could find no documentation supporting this theory.
I am also currently only testing this in the emulator. I am unsure if that would have an effect on this, though I cannot see how it would.
Is there anything that I am missing in order to properly receive the network logs via the DeviceAdminReceiver?
I'm afraid there's no elegant solution.
This limitation looks like it was made intentionally. As you can see in the sources, the event is triggered when hard-coded thresholds are reached. It's either 1200 events or 1.5H timeout, whichever comes first. I did not manage to find any usable hooks in the NetworkLogger. They definitely did not want users to meddle with it.
The only option I see is to use reflection to get access to the hidden API.
The most straightforward, IMHO, is to get a handle to the IIpConnectivityMetrics service and use it to subscribe to the network events. I did not test this solution myself, though.
It seems like you can now force retrieve log for debugging purposes as described here: https://developer.android.com/work/dpc/logging#development_and_testing
Quote from the documentation:
While you’re developing and testing, you might want to receive onNetworkLogsAvailable() callbacks without having to browse hundreds of web pages. In Android 9.0 (API level 28) or higher, you can make a few sample network requests and force the system to send a logs-available callback. Run the following Android Debug Bridge (adb) command in your terminal:
adb shell dpm force-network-logs
The system limits how frequently you can use the tool and reports any intentional slowing in the terminal output. If there aren’t any logs to retrieve, your DPC doesn’t receive a callback.
Since API 21, Google has been adding features to android.telecom in general, especially by implementing more members of TelecomManager and the addition of InCallService. This last one is is supposed to allow non-system, 3rd-party apps to provide and replace the functionality of the system Calls app in-call screen - the Window that pops up and allows action on EXTRA_STATE_OFFHOOK or EXTRA_STATE_RINGING broadcasts (i.e. incoming and outgoing phone calls).
Currently, only this screen has full control of ringing and active calls and associated system callbacks with fine-grained information, by means of the root-restricted MODIFY_PHONE_STATE permission and a lot of secured AOSP code not even accessible by reflection. It's notably one of the most changed pieces of code in different manufacturers' ROM flavours, together with the launcher, contacts and camera.
This is all very pretty but...
How do you actually develop a 3rd-party InCallService?
Namely:
How do you get notified about, and acquire instances of GSM Calls
How does one answer these calls
What is the life-cycle of the callbacks on this class
Does Google provide any actual tutorial for this that I haven't found
I won't ask answers for all of these at once, but any one answer probably associates to the other questions. This is broad but intrinsically it needs to be: there's no example on the web I've stumbled upon other than AOSP-code, and that code is based on the assumption of root-privileges, which makes it unusable for 3rd-party app development purposes.
How do you get notified about, and acquire instances of GSM Calls
First, the user will need to select your app as the default Phone app. Refer to Replacing default Phone app on Android 6 and 7 with InCallService for a way to do that.
You also need to define an InCallService implementation the system will bind to and notify you about the call:
<service
android:name=".CallService"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>
There you should handle at least onCallAdded (set up listeners on Call, start your UI - activity - for the call) and onCallRemoved (remove listeners).
How does one answer these calls
If the user wants to answer the call, you need to invoke the method Call#answer(int) with VideoProfile.STATE_AUDIO_ONLY for example.
What is the life-cycle of the callbacks on this class
Check out Call.Callback for events that can happen with a single call.
Does Google provide any actual tutorial for this that I haven't found
I don't know about Google, but you can check out my simplified example https://github.com/arekolek/simple-phone
Follow the advice from the second comment of Replacing in call app. In addition you need a service that implements the InCallService interface. When a call arrives the onCallAdded(Call call) method will be called, giving you a reference to the call object.
<service
android:name=".InCallServiceImplementation"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>
Once you have the call object, answering it's as simple as call.answer(). I suggest that when you get the stuff above working, run a couple of test calls to get to know when the different callbacks are invoked.
Regarding tutorials, I couldn't find any when I was looking into this, but that was over a year ago...
Hope this helps!
I guess Google must've read this question, because apparently on Android 8, a new permission finally allows answering calls through a 3rd party dev-facing permission.
android.permission.ANSWER_PHONE_CALLS (...) allows apps to answer
incoming phone calls programmatically
No details yet though, since the documentation for API 26 hasn't been released yet. I'll make sure to update this answer when they do.
EDIT: user arekolek provided an answer that works perfectly on the original API version of this question (at the time of asking, API was 23, even though the question mentions API 21), thus he gets the tick for right answer. Refer to his answer if you want to implement an incall screen that targets minimum SDK of 23. Note you might need API-dependant code or compat library tweaks if you want it to work on more recent APIs that deprecate (or restrict) usage of the provided sample code. the github repo works as I initially intended.
I would recommend you to see this project to build a dialer app for Android.
https://github.com/HiddenPirates/Dialer
I'm developing a custom home screen launcher. As part of its functionality I would like to be able to show unread notification badges.
Instead of implementing my own API for this, I would like to hook onto existing standards. The most widely used is the one for Samsung's TouchWiz launcher.
It works through a ContentProvider with the authority com.sec.badge. Now, on Samsung devices, I can easily use a ContentObserver to observe changes to Samsung's ContentProvider and it works perfectly. However, on devices without an existing ContentProvider (i.e. non-Samsung devices) I would like to provide my own ContentProvider for this purpose. This also works perfectly to capture inserts from other apps.
However, when I roll my own ContentProvider I naturally get an INSTALL_FAILED_CONFLICTING_PROVIDER error when trying to install on Samsung devices.
I fully understand why this is happening since Android wants to avoid having conflicts in the providers.
What I would want help with is a workaround. Is it possible to somehow register my ContentProvider dynamically instead of declaring it in AndroidManifest.xml? That way, I could first check if the authority is already taken, and if so go with an Observer. Otherwise, register my own ContentProvider and go with that.
I realize that this might be bad practice but I don't really see any other way. Some apps (such as Facebook) also implement Sony's badge API that works through Broadcasts which avoids conflict, but not nearly as many existing apps use this process.
I've tried this:
ContentProvider test = new SamsungContentProviderSpoof();
ProviderInfo providerInfo = new ProviderInfo();
providerInfo.authority = "com.sec.badge";
providerInfo.enabled = true;
providerInfo.exported = true;
test.attachInfo(this, providerInfo);
but that obviously fails. I guess I need to somehow access some system ContentResolver and register myself there, but I don't know how.
I would like to hook onto existing standards
There are no existing standards. A few vendors have done their own thing, and that's it.
The most widely used is the one for Samsung's TouchWiz launcher.
Note that this mechanism is undocumented (AFAICT) and unsupported (outside of select Samsung partners).
Is it possible to somehow register my ContentProvider dynamically instead of declaring it in AndroidManifest.xml? That way, I could first check if the authority is already taken, and if so go with an Observer. Otherwise, register my own ContentProvider and go with that.
You are welcome to say that it is disabled (android:enabled="false") in the manifest, then conditionally enable it later using PackageManager and setComponentEnabledSetting(). You would know that you needed to do this by either trying to communicate with the existing provider (e.g., registering your observer) and getting an expected error, or by interrogating PackageManager to see if the provider exists.
However, not only will you need to claim that you are Samsung in terms of the provider, but also in terms of the custom permissions. That will break on the "L" Developer Preview, and probably going forward, for much the same reason that you ran into with the conflicting provider. At the present time, there is no workaround for this that I am aware of.
I am implementing a login system for an Android application utilizing the built-in accounts system (with the AccountManager APIs).
All is well and good on Android 2.2+, but on Android 2.1 not including a SyncAdapter causes reboots in the account settings screen (see http://code.google.com/p/android/issues/detail?id=5009 and AccountManager without a SyncAdapter?)
To get around this I implemented a stub SyncAdapter, which just returns null from IBinder onBind(Intent intent), and added the relevant stuff to the manifest. This resolves the reboot issue on Android 2.1.
However it introduces a further problem: after an account is added the Android system will, sometime later, initiate an account sync. Although no errors occur (indeed my SyncAdapter does nothing, it has no way to cause errors unless by returning null), the sync icon stays stuck in the notification bar at the top. This results in the Android sync system maintaining a permanent wake-lock, preventing the device from sleeping.
The account does not list any syncable components in the account settings screen (under the 'Data and synchronization' header), and always displays 'Sync is off' for the sync status in the list of accounts (even while the sync icon is visible in the notifications bar). Disabling account syncing does not remove the problem. Removing the account stops the problem.
My guess is I should not be returning null. Should I be returning a basic implementation of ThreadedSyncAdapter? Any help getting an account system without an associated sync working properly on 2.1 and 2.2+ is much appreciated.
Since this is the only question I've seen related to this problem, here's a >year late answer. I also came across the permanent wake-lock problem due to the android system syncing my custom account automatically.
The best way to handle this, which requires minimum code and actually makes it so the account never syncs unless specifically called to sync in code:
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
Now this requires that the moment you create your account you call this static method. Whereas the first parameter being the account to set this setting for, the second parameter being the used contentprovider's authority, and the third being the integer that when set to a positive number enables syncing, when set to 0 disables syncing and when set to anything else makes it unknown. The authority to use can be found inside your "sync_something.xml" under the contentAuthority attribute, which is used by your SyncAdapter :
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts"
android:accountType="com.myapp.account"/> <!-- This being your own account type-->
The above xml file is specified inside the service part of your AndroidManifest.xml:
<service android:name=".DummySyncAdapterService"
exported="true"
android:process=":contacts">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="#xml/sync_something" /> <!--This points to your SyncAdapter XML-->
</service>
This is the code snippet I use to create my custom account inside my LoginActivity:
Account account = new Account("John Doe", "com.myapp.account");
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
AccountManager am = AccountManager.get(LoginActivity.this);
boolean accountCreated = am.addAccountExplicitly(account, "Password", null);
Bundle extras = LoginActivity.this.getIntent().getExtras();
if(extras != null){
if (accountCreated) {
AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, "John Doe");
result.putString(AccountManager.KEY_ACCOUNT_TYPE, "com.myapp.account");
response.onResult(result);
}
}
The great part of this is that when the system tries to sync the service, it checks if the service is syncable first, if it is set to false it cancels the syncing. Now you don't have to create your own ContentProvider nor does your ContentProvider get shown under Data and Synchronization. However you do need to have a stub implementation of AbstractThreadedSyncAdapter which returns an IBinder inside it's onBind method. And last but not least it makes it so that an user can't enable syncing or use the "Sync Now" button for this account unless you've added the functionality inside your app.
I sort of solved my own problem: you cannot return null from the onBind method of your service - you must return the IBinder of an AbstractThreadedSyncAdapter.
This has the undesired effect of adding an entry into the Data and Synchronization section of the account settings page, even though my implementation of AbstractThreadedSyncAdapter does nothing; I was hoping to avoid this.
To summarize, in order to make use of the accounts system in Android you must:
Implement a service that has an IntentFilter for android.content.SyncAdapter.
This service must return the IBinder of an AbstractThreadedSyncAdapter implementation from it's onBind method.
This then necessitates that you have a ContentProvider (can just be a stub implementation) that is referred to as the contentAuthority in your SyncAdapter XML file.
This has the down-side that your ContentProvider is listed under the Data and Synchronization header on your account settings page.