So I followed the tutorial on creating a SyncAdapter without a real ContentProvider and an Account (from here) but came across a problem I just can't fix.
When calling ContentResolver.requestSync() to manually force a sync, it seems, that the SyncAdapter I implemented just doesn't get called.
Please note, that I created all the stub classes (StubAuthenticator, StubProvider, AuthenticatorService) and the SyncService exactly how they got created in the mentioned tutorial. If you need me to post the code either way, I will edit this post.
Android Manifest.xml
<application
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
...
<service
android:name=".datasync.SyncService"
android:exported="true"
android:process=":sync">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="#xml/sync_adapter"/>
</service>
<service
android:name=".datasync.AuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="#xml/authenticator"/>
</service>
<provider
android:name=".datasync.StubProvider"
android:authorities="myexample.com.provider"
android:enabled="true"
android:exported="false"
android:label="DataSyncContentProvider"
android:syncable="true"/>
</application>
sync_adapter.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="myexample.com.provider"
android:accountType="myexample.com.account"
android:userVisible="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"/>
authenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="myexample.com.account"
android:label="MyExampleApp"/>
DataSyncAdapter.java
public class DataSyncAdapter extends AbstractThreadedSyncAdapter{
public DataSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
SyncResult syncResult) {
Log.d(MainActivity.TAG, "onPerformSync: Called");
//do sync stuff here
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity{
/**
* Tag for own application.
*/
public static final String TAG = "com.myexample";
#Override
protected final void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//create UI here...
Bundle syncExtras = new Bundle();
//add additional sync stuff (internal)
DataSyncUtil.createSyncRequest(this, syncExtras);
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[]
grantResults) {
switch (requestCode) {
case PERMISSIONS_GET_ACCOUNTS:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Bundle syncExtras = new Bundle();
//add additional sync stuff (internal)
DataSyncUtil.createSyncRequest(this, syncExtras);
} else {
//TODO handle permission denied
}
return;
}
}
}
DataSyncUtil.java
public class DataSyncUtil {
public static void createSyncRequest(Activity activity, Bundle extras) {
Log.d(MainActivity.TAG, "createSyncRequest: Called");
String authority = "myexample.com.provider";
Account dummy = getDummySyncAccount(activity);
if (dummy != null) {
if (ContentResolver.isSyncPending(dummy, authority) ||
ContentResolver.isSyncActive(dummy, authority)) {
Log.i(MainActivity.TAG, "SyncPending, canceling");
ContentResolver.cancelSync(dummy, authority);
}
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(dummy, authority, extras);
}
}
public static Account getDummySyncAccount(Activity activity) {
String auth_type = "myexample.com.account;
Account dummy = null;
AccountManager accountManager = (AccountManager) activity.getSystemService(ACCOUNT_SERVICE);
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.GET_ACCOUNTS) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.GET_ACCOUNTS},
MainActivity.PERMISSIONS_GET_ACCOUNTS);
} else {
Account[] existing = accountManager.getAccountsByType(auth_type);
if (existing != null && existing.length > 0) {
//TODO handle more than one account
Log.d(MainActivity.TAG, "getDummySyncAccount: Account already exists and is getting returned");
dummy = existing[0];
} else {
Log.d(MainActivity.TAG, "getDummySyncAccount: Account has to be created");
// Create the account type and default account
dummy = new Account(ACCOUNT, auth_type);
// Get an instance of the Android account manager
/*
* Add the account and account type, no password or user data
* If successful, return the Account object, otherwise report an error.
*/
if (accountManager.addAccountExplicitly(dummy, null, null)) {
ContentResolver.setIsSyncable(dummy, "myexample.com.content", 1);
ContentResolver.setSyncAutomatically(dummy, "myexample.com.content", true);
} else {
/*
* The account exists or some other error occurred.Log this, report it,
* or handle it internally.
*/
}
}
}
Log.d(MainActivity.TAG, "getDummySyncAccount: "+dummy.name);
return dummy;
}
}
The code executes just fine up until ContentResolver.requestSync() in the DataSyncUtil class, but the DataSyncAdapter never gets called.
I'd appreciate any help on this :)
FYI: This is my first big scale Android project, so I'm rather inexperienced.
It appears I made a rookie mistake. onPerformSync() does indeed get called. But I had the filter "Show only selected application" in my android monitor set, so that the Log.d in the background sync service didn't show.
This answer helped me out.
Related
I am creating an Unity app which will be controlling Numato GPIO USB powered controller through smartphone USB connection. Since I have to connect the controller to phone I have no debug log so I have no idea what is going on. Thus, I include plugin code and a custom manifest which I use in Unity.
I get questioned by the App if I want to grant permission to control the device (Shows right device name etc) and after I grant the permission app crashes immediately.
Is there a way to check what causes the error? Or maybe I don't see something obvious here.
public class PluginInstance extends Activity {
private static Activity unityActivity;
private static Context unityContext;
public Gpio laser1;
public static void receiveUnityActivity(Activity tActivity) {
unityActivity = tActivity;
}
public static void receiveUnityContext(Context tContext) { unityContext = tContext; }
//Debugging purposes
public void Toast(String msg) {
Toast.makeText(unityActivity, msg, Toast.LENGTH_SHORT).show();
}
//Action Usb Permission
public static final String ACTION_USB_PERMISSION = "com.unity3d.player.UnityPlayerActivity.USB_PERMISSION";
public DevicesManager mDevicesManager;
//USB permission
public final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
Bundle deviceIndexBundle = intent.getExtras();
if (deviceIndexBundle == null) {
return;
}
int deviceIndex = deviceIndexBundle.getInt(AppConstant.EXTRA_DEVICE_INDEX);
Toast.makeText(unityActivity, "Usb permission granted", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(unityActivity, "Usb permission declined", Toast.LENGTH_SHORT).show();
}
unregisterReceiver(mUsbReceiver);
}
}
}
};
public void InstantiateManager() {
mDevicesManager = DevicesManager.getInstance();
}
public void GetGpios() {
NumatoUSBDevice numatoUSBDevice = mDevicesManager.getDevices().get(0);
Gpio lazer1 = numatoUSBDevice.mGpios.get(1);
}
public void GpioOn() {
laser1.setCurrentOutputState(true);
laser1.setState(true);
}
public void GpioOff() {
laser1.setCurrentOutputState(false);
laser1.setState(false);
}
public void EnumerateDevices() {
UsbManager manager = (UsbManager) unityActivity.getSystemService(Context.USB_SERVICE);
int index = 0;
ArrayList<UsbDevice> cdcAcmDevices = CdcAcmDriver.ListDevices(manager);
mDevicesManager.clearDevices();
ArrayList<Integer> supportedDevices = NumatoUSBDevice.GetSupportedProductIds();
if(!cdcAcmDevices.isEmpty()){
for (UsbDevice cdcAcmDevice : cdcAcmDevices){
int vendorId = cdcAcmDevice.getVendorId();
if(vendorId == NumatoUSBDevice.VID_NUMATOLAB && supportedDevices.contains(cdcAcmDevice.getProductId())){
mDevicesManager.addDevice(new NumatoUSBDevice(index, cdcAcmDevice, manager));
index++;
}
}
}
}
public void MakeConnection() {
NumatoUSBDevice numatoUSBDevice = mDevicesManager.getDevices().get(0);
UsbManager manager = (UsbManager) unityActivity.getSystemService(Context.USB_SERVICE);
//TODO unityContext in mPermissionIntent
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(unityContext, 0,
new Intent(ACTION_USB_PERMISSION).putExtra(AppConstant.EXTRA_DEVICE_INDEX, 0), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
unityActivity.registerReceiver(mUsbReceiver, filter);
manager.requestPermission(numatoUSBDevice.getDevice(), mPermissionIntent);
}
}
And the manifest file:
<?xml version="1.0" encoding="utf-8"?>
<!-- GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="#style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="#xml/device_filter"/>
</activity>
</application>
</manifest>
Edit: I managed to access debug log through wifi and this is what I get:
FATAL EXCEPTION: main
Process: com.jonquil.A2NumatoController, PID: 16837
java.lang.RuntimeException: Error receiving broadcast Intent { act=com.unity3d.player.UnityPlayerActivity.USB_PERMISSION flg=0x10 (has extras) } in com.jonquil.unityplugin.PluginInstance$1#7d45921
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1689)
at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7838)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.content.Context.unregisterReceiver(android.content.BroadcastReceiver)' on a null object reference
at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:769)
at com.jonquil.unityplugin.PluginInstance$1.onReceive(PluginInstance.java:63)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1679)
... 9 more
This looks like there is a problem with broadcast receiver since error is caused by invoking method on a null object reference.
I have an Android app that writes .txt files to the downloads folder based off your inputs, a listview that displays all your downloads and lets you click to view them (I have this working), and I'm now trying to code a way to upload them to Google Drive. I have done the developer's verification process with the SHA1 key so it should be fine as far as that goes. I often see this demo app that takes pictures and uploads them being recommended and it looks like a good code to start with and modify, but when I run it on my phone, it doesn't work-it just repeatedly asks me to select my account endlessly. The java code inside the project itself is this (I'm pretty new and don't quite understand how all of it works, but this is for the google demo in the link):
public class MainActivity extends Activity implements ConnectionCallbacks,
OnConnectionFailedListener {
private static final String TAG = "drive-quickstart";
private static final int REQUEST_CODE_CAPTURE_IMAGE = 1;
private static final int REQUEST_CODE_CREATOR = 2;
private static final int REQUEST_CODE_RESOLUTION = 3;
private GoogleApiClient mGoogleApiClient;
private Bitmap mBitmapToSave;
/**
* Create a new file and save it to Drive.
*/
private void saveFileToDrive() {
// Start by creating a new contents, and setting a callback.
Log.i(TAG, "Creating new contents.");
final Bitmap image = mBitmapToSave;
Drive.DriveApi.newDriveContents(mGoogleApiClient)
.setResultCallback(new ResultCallback<DriveContentsResult>() {
#Override
public void onResult(DriveContentsResult result) {
// If the operation was not successful, we cannot do anything
// and must
// fail.
if (!result.getStatus().isSuccess()) {
Log.i(TAG, "Failed to create new contents.");
return;
}
// Otherwise, we can write our data to the new contents.
Log.i(TAG, "New contents created.");
// Get an output stream for the contents.
OutputStream outputStream = result.getDriveContents().getOutputStream();
// Write the bitmap data from it.
ByteArrayOutputStream bitmapStream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.PNG, 100, bitmapStream);
try {
outputStream.write(bitmapStream.toByteArray());
} catch (IOException e1) {
Log.i(TAG, "Unable to write file contents.");
}
// Create the initial metadata - MIME type and title.
// Note that the user will be able to change the title later.
MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
.setMimeType("image/jpeg").setTitle("Android Photo.png").build();
// Create an intent for the file chooser, and start it.
IntentSender intentSender = Drive.DriveApi
.newCreateFileActivityBuilder()
.setInitialMetadata(metadataChangeSet)
.setInitialDriveContents(result.getDriveContents())
.build(mGoogleApiClient);
try {
startIntentSenderForResult(
intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0);
} catch (SendIntentException e) {
Log.i(TAG, "Failed to launch file chooser.");
}
}
});
}
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
// Connect the client. Once connected, the camera is launched.
mGoogleApiClient.connect();
}
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
#Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_CODE_CAPTURE_IMAGE:
// Called after a photo has been taken.
if (resultCode == Activity.RESULT_OK) {
// Store the image data as a bitmap for writing later.
mBitmapToSave = (Bitmap) data.getExtras().get("data");
}
break;
case REQUEST_CODE_CREATOR:
// Called after a file is saved to Drive.
if (resultCode == RESULT_OK) {
Log.i(TAG, "Image successfully saved.");
mBitmapToSave = null;
// Just start the camera again for another photo.
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
REQUEST_CODE_CAPTURE_IMAGE);
}
break;
}
}
#Override
public void onConnectionFailed(ConnectionResult result) {
// Called whenever the API client fails to connect.
Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
if (!result.hasResolution()) {
// show the localized error dialog.
GoogleApiAvailability.getInstance().getErrorDialog(this, result.getErrorCode(), 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization
// dialog is displayed to the user.
try {
result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
} catch (SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "API client connected.");
if (mBitmapToSave == null) {
// This activity has no UI of its own. Just start the camera.
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
REQUEST_CODE_CAPTURE_IMAGE);
return;
}
saveFileToDrive();
}
#Override
public void onConnectionSuspended(int cause) {
Log.i(TAG, "GoogleApiClient connection suspended");
}
}
And in the manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms.drive.sample.quickstart"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name="com.google.android.gms.drive.sample.quickstart.MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
</manifest>
And with that, my main two questions are:
Any idea why the quick start project from Google is behaving the way it is?
If it's not too much, but what parts of the java code do I need to modify to make it upload a .txt file after pressing a button?
You are supposed to create a project in Google Api COnsole,then enable Drive API. Then create required credentials(SHA-1, key, CLIENT-ID) for the the android app to connect with drive api.
After getting all the credentials, include them in Manifest file and String File.
Follow the instructions here, QuickStart Android Instructions
I am developing an App to check Wifi points.
I am getting error "java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results" at wifiManager.getScanResults() even though I already declared those permissions.
main activity
public class MainActivity extends AppCompatActivity {
WifiManager wifiManager;
String[] wifis;
WifiReceiver wifiReceiver;
ListView wifiListView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wifiListView = (ListView) findViewById(R.id.wifi_list);
wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiReceiver = new WifiReceiver();
wifiManager.startScan();
}
protected void onPause() {
unregisterReceiver(wifiReceiver);
super.onPause();
}
protected void onResume() {
registerReceiver(wifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
super.onResume();
}
private class WifiReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
List<ScanResult> wifiScanList = wifiManager.getScanResults();
wifis = new String[wifiScanList.size()];
for (int i = 0; i < wifiScanList.size(); i++) {
wifis[i] = wifiScanList.get(i).toString();
}
wifiListView.setAdapter(new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, wifis));
}
}
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sample">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
I am on SDK 6.0
I observed similar question, but solution does not apply since I already declared permission.
Anyone know what might be problem? Thank you.
In Android M, you need to ask for the permission which is defined as dangerous in PermissionModel to the user before start using each time, it as such:
private boolean mayRequestLocation() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (checkSelfPermission(ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
return true;
}
if (shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)) {
Snackbar.make(mView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok, new View.OnClickListener() {
#Override
#TargetApi(Build.VERSION_CODES.M)
public void onClick(View v) {
requestPermissions(new String[]{ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
}
});
} else {
requestPermissions(new String[]{ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
}
return false;
}
Add this to your Activity:
private static final int REQUEST_FINE_LOCATION=0
and load it during runtime with:
loadPermissions(Manifest.permission.ACCESS_FINE_LOCATION,REQUEST_FINE_LOCATION);
To evaluate the results of your permission request, you can override onRequestPermissionsResult method:
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_FINE_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// The requested permission is granted.
}
else{
// The user disallowed the requested permission.
}
return;
}
}
MADAO is right: you should turn on GPS to get the WIFI access point list.
But I'm not sure about PEERS_MAC_ADDRESS. If you look at the source code (line 957):
/**
* Return the results of the most recent access point scan, in the form of
* a list of {#link ScanResult} objects.
* #return the list of results
*/
public List<ScanResult> getScanResults(String callingPackage) {
enforceAccessPermission();
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
boolean canReadPeerMacAddresses = checkPeersMacAddress();
boolean isActiveNetworkScorer =
NetworkScorerAppManager.isCallerActiveScorer(mContext, uid);
boolean hasInteractUsersFull = checkInteractAcrossUsersFull();
long ident = Binder.clearCallingIdentity();
try {
if (!canReadPeerMacAddresses && !isActiveNetworkScorer
&& !isLocationEnabled()) {
return new ArrayList<ScanResult>();
}
if (!canReadPeerMacAddresses && !isActiveNetworkScorer
&& !checkCallerCanAccessScanResults(callingPackage, uid)) {
return new ArrayList<ScanResult>();
}
if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return new ArrayList<ScanResult>();
}
if (!isCurrentProfile(userId) && !hasInteractUsersFull) {
return new ArrayList<ScanResult>();
}
return mWifiStateMachine.syncGetScanResultsList();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
The first if is checking canReadPeerMacAddresses which the code for checkPeersMacAddress() is:
/**
* Returns true if the caller holds PEERS_MAC_ADDRESS.
*/
private boolean checkPeersMacAddress() {
return mContext.checkCallingOrSelfPermission(
android.Manifest.permission.PEERS_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
}
If you add the permission you can bypass if (!canReadPeerMacAddresses && !isActiveNetworkScorer && !isLocationEnabled()) {. I've tested but I cannot get WIFI MAC list by just using the permission and disabling location.
ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION is necessary. To get a valid result, you also have to turn on GPS or get a PEERS_MAC_ADDRESS permission like Setting.
I am following a tutorial for adding a user account to the Android AccountManager.
In my main activity, I have the following method:
private void addNewAccount(String accountType, String authTokenType) {
Log.d(TAG,"addNewAccount called");
final AccountManagerFuture<Bundle> future = mAccountManager.addAccount(accountType, authTokenType, null, null, this, new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bnd = future.getResult();
Log.d("ACME", "AddNewAccount Bundle is " + bnd);
} catch (Exception e) {
e.printStackTrace();
}
}
}, null);
}
This method is being called, as I see the log in the logcat. Now my AbstractAccountAuthenticator implementation is as follows:
public class AcmeAuthenticator extends AbstractAccountAuthenticator {
private String TAG = "AcmeAuthenticator";
private final Context mContext;
public AcmeAuthenticator(Context context) {
super(context);
this.mContext = context;
}
#Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Log.d("acme", TAG + "> addAccount");
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
The above method is never called. The following is the service I have created for it:
public class AcmeAuthenticatorService extends Service {
#Override
public IBinder onBind(Intent intent) {
AcmeAuthenticator authenticator = new AcmeAuthenticator(this);
return authenticator.getIBinder();
}
}
And my manifest definition is as follows:
<activity android:name="com.exercise.accountmanagerstudy.accountAuthenticator.AuthenticatorActivity" android:label="#string/login_label"/>
<service android:name=".accountAuthenticator.AcmeAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="#xml/authenticator" />
</service>
<!-- client -->
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<!-- Authenticator -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
I am not getting an compiler errors, the addAccount override in the AbstractAccountAuthenticator implementation is not called. from the main activity addNewAccount method. I have researched around for a few links here and here.
Any help will be appreciated.
Ok, so I finally did figure it out. Apparently, the authenticator.xml file for AcmeAuthenticator has a field called accountType:
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.exercise.accountmanagerstudy"
android:icon="#drawable/ic_launcher"
android:smallIcon="#drawable/ic_launcher"
android:label="#string/label"
android:accountPreferences="#xml/prefs"/>
When I was calling addNewAccount in my main activity, I was supposed to pass the exact value of the accountType in the above mentioned xml as the accountType argument. Phew, that took me quite sometime and hope it helps someone else :-).
For some reason I cannot bind to my service? I need the onServiceConnected() method to run so that I can use my AIDL interface. What am I doing wrong?
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d("BindingService", "Service trying to bind!");
sendService = ISendMessageService.Stub.asInterface((IBinder) service);
boundToService = true;
pendingFragment.bindToService();
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
sendService = null;
boundToService = false;
pendingFragment.unbindService();
}
};
The main activity methods:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
schedulerFragment = new SchedulerFragment();
pendingFragment = new PendingFragment();
fm = getFragmentManager();
titleBar = getActionBar();
titleBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
scheduleTab = titleBar.newTab();
pendingTab = titleBar.newTab();
scheduleTab.setText("Schedule");
pendingTab.setText("Pending");
scheduleTab.setTabListener(new MyTabListener(schedulerFragment));
pendingTab.setTabListener(new MyTabListener(pendingFragment));
titleBar.addTab(scheduleTab);
titleBar.addTab(pendingTab);
titleBar.selectTab(scheduleTab);
Boolean success = getApplicationContext().bindService(new Intent(SendMessageService.class.getName()),
serviceConnection, Context.BIND_AUTO_CREATE);
Log.d("ServiceConnection", success.toString());
}
#Override
public void onStop() {
super.onStop();
if (boundToService) {
unbindService(serviceConnection);
}
}
#Override
public void onPause() {
super.onPause();
if (boundToService) {
unbindService(serviceConnection);
}
}
This is the service's onBind() function:
#Override
public IBinder onBind(Intent intent) {
mBinder = new ISendMessageService.Stub(){
#Override
public void deleteMessage(int index) throws RemoteException {
TimedMessage m = schedule.get(index);
schedule.get(index).alarm.cancel(m.intent);
schedule.remove(m);
}
#Override
public void cancelAllMessages() throws RemoteException {
for(TimedMessage m : schedule){
m.alarm.cancel(m.intent);
}
schedule.clear();
}
};
return mBinder;
The manifest:
<service
android:name="com.pearhill.messagesender.SendMessageService"
android:enabled="true"
android:exported="true"
android:process=":remote" >
<intent-filter>
<action android:name="com.pearhill.messagesender.ISendMessageService.aidl" />
</intent-filter>
</service>
Here are the links for full description:
https://developer.android.com/guide/components/bound-services.html
https://developer.android.com/guide/components/aidl.html#Calling
As a key point make sure your aidl is copied over to your application properly. Clean the project and remove aidl.
Then copy the aidl over to your application in aidl folder, but package name same of remote aidl. Rebuild. You need not to mention service in your androidManifest of calling application; it will be taken care by aidl interface that you copied over.
Make sure service is also an app and not a library (jar type). At least we couldn't make it work with service in jar file. You may choose "No activity" in the new app wizard, if you are not planning to create local activity. In that case you can create apk using "Build->Build Apk" menu in Android studio for service based app. Make sure to install the service apk before running BindService from remote app. You need not to run service as BindService will also start the service, but you need to install apk, so that it is available in system. You can use adb install path/myapkname.apk.
Action name is not must, but this is how we did bind the service, where "com.example.RemoteService.BIND" was action name for service:
String pkg = IRemoteService.class.getPackage().getName();
//get the class name from the interface package
String interfaceName = IRemoteService.class.getName();
String clsName = interfaceName.replace("IRemoteService", "RemoteService");
Intent it = new Intent("com.example.RemoteService.BIND");
it.setClassName(pkg, clsName);
boolean bRet = getApplicationContext().bindService(it, mConnection, Service.BIND_AUTO_CREATE);
Log.d("IRemote", "IRemoteService Service.BIND_AUTO_CREATE return: " + bRet);
This is what was used in eclipse and worked, but failed in Android studio because of explicit intent:
Intent it = new Intent( );
it.setClassName("com.example.aidlservice",
"com.example.aidlservice.MyService");
//optional
it.setAction("com.example.RemoteService.BIND");
//binding to remote service
boolean bRet = bindService(it, mServiceConnection, Service.BIND_AUTO_CREATE);
Log.d("IRemote", "Service.BIND_AUTO_CREATE");
Here is manifest of service app. You may want to add permissions for security reasons:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testaidl">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service android:name=".RemoteService" android:process=":remote" android:exported="true">
<intent-filter>
<action android:name="com.example.RemoteService.BIND" />
</intent-filter>
</service>
</application>
</manifest>