I am trying to get a phone number from the incoming call, and for Android, I've been trying to implement this answer, but I've been struggling because the answer is not full, has no example, and is very abstract, which makes it hard for a person without any knowledge about Platform Channel.
What I've tried:
I've implemented pigeon code in a ServiceReceiver, declared a variable that I want to update and pass to the flutter code, however when I receive a call, the app doesn't even ask for any permission, which makes me think that the onReceive() function is not called when the phone receives an incoming call.
Here is the code:
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './output.dart';
void main() {
runApp(MyApp());
}
// void onClick() async {
// SearchRequest request = SearchRequest()..query = 'test';
// Api api = Api();
// SearchReply reply = await api.search(request);
// print('reply: ${reply.result}');
// }
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel('battery.ttsollution.com');
String _phoneNumber = '';
String _batteryLevel = 'Unknown battery level.';
void onClick() async {
SearchRequest request = SearchRequest()..query = 'test';
Api api = Api();
SearchReply reply = await api.search(request);
print('reply: ${reply.result}');
}
Future<void> _getPhoneNumber() async {
String phoneNumber;
try {
final int result = await platform.invokeMethod('getPhoneNumber');
phoneNumber = "Phone number: $result";
} catch (e) {
phoneNumber = "Failed to get the phone number due to: $e";
}
setState(() {
_phoneNumber = phoneNumber;
});
}
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: Text('Get Phone Number'),
onPressed: onClick,
),
Text(_batteryLevel),
],
),
),
),
);
}
}
MainActivity.java:
package com.example.phone_application_java;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.telephony.TelephonyManager;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "battery.ttsollution.com";
#RequiresApi(api = VERSION_CODES.M)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Pigeon.Api.setup(getFlutterEngine().getDartExecutor().getBinaryMessenger(), new ServiceReceiver.MyApi());
}
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler((call, result) -> {
// Note: this method is invoked on the main thread.
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel();
if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
});
}
private int getBatteryLevel() {
int batteryLevel = -1;
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
Intent intent = new ContextWrapper(getApplicationContext()).registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
return batteryLevel;
}
}
ServiceReceiver.java:
package com.example.phone_application_java;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.os.Bundle;
import com.example.phone_application_java.Pigeon;
public class ServiceReceiver extends BroadcastReceiver {
static String phoneNumber = "Hiya";
static class MyApi implements Pigeon.Api {
#Override
public Pigeon.SearchReply search(Pigeon.SearchRequest arg) {
Pigeon.SearchReply reply = new Pigeon.SearchReply();
reply.setResult(ServiceReceiver.phoneNumber);
return reply;
}
}
#SuppressLint("UnsafeProtectedBroadcastReceiver")
#Override
public void onReceive(Context context, Intent intent) {
TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
telephony.listen(new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
System.out.println("incomingNumber : " + incomingNumber);
phoneNumber = incomingNumber;
}
}, PhoneStateListener.LISTEN_CALL_STATE);
}
}
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.phone_application_java">
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<application android:label="phone_application_java" android:icon="#mipmap/ic_launcher">
<receiver android:name=".ServiceReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="#style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="#style/NormalTheme" />
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="#drawable/launch_background" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
</manifest>
Flutter Doctor:
PS E:\Flutter Projects\phone_application_java> flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.6, on Microsoft Windows [Version 10.0.19042.928], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[√] Chrome - develop for the web
[√] Android Studio (version 4.1.0)
[√] Connected device (3 available)
• No issues found!
Related
I am working on Nordic NRF52840 periodic advertising and scan the device with Android app Android 10. I ask a question yesterday then I rewrite my code using the tutorial on the internet, basically my app is very easy, press the button then the app will scan and print the ble device on log, but I still can't find my device I don't know what's wrong with my code I add a ton of permission in Manifest.xml but nothing changed I still can't find the device I want. Update: I can find some device,but not my device, I still can't find my device there are a few question I want to ask
I can't find my device, but can find other device.
how to stop scanning, I add a button to stop scanning, but it won't stop.
how to get device name, when I call getName() error pop up and the app closed
getName error
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tryble_scanner">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission
android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
/>
<uses-permission
android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth"
android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.TryBle_scanner">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.java
package com.example.tryble_scanner;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mBluetoothLeScanner = null;
public static final int REQUEST_BT_PERMISSIONS = 0;
public static final int REQUEST_BT_ENABLE = 1;
private boolean mScanning = false;
private Handler mHandler = null;
private ScanCallback mLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
//super.onScanResult(callbackType, result);
BluetoothDevice btdevice = result.getDevice();
Log.d("BLE", btdevice.getAddress());
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.d("BLE", "error");
}
};
private ScanCallback mLeScanCallback2=new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
Log.d("BLE","scan stop");
}
#Override
public void onScanFailed(int errorCode) {
Log.d("BLE","stop scan failed");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnScan = (Button) findViewById(R.id.btnScan);
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
this.mHandler = new Handler();
}
public void stop_scan(View view) {
Log.d("Ble","scan stop pressed");
// mBluetoothLeScanner.stopScan(mLeScanCallback2);
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback2);
}
public void onBtnScan(View view) {
Log.i("Btn","get click");
checkBTPermission();
String[] names=new String[]{"Auden test"};
List<ScanFilter> filters=null;
if(names != null){
filters=new ArrayList<>();
for(String name:names){
ScanFilter filter=new ScanFilter.Builder().setDeviceName(name).build();
filters.add(filter);
}
}
if(mBluetoothLeScanner==null){
Log.i("BLE","could not get scanner");
}else{
mBluetoothLeScanner.startScan(mLeScanCallback); //filters,scanSettings,mLeScanCallback
}
}
private void checkBTPermission(){
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
int pc=this.checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION");
pc+=this.checkSelfPermission("Manifest.permission.ACCESS_COARSE_LOCATION");
if(pc!=0){
this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},1001);
}else {
Log.d("BLE","checkBT permission");
}
}
}
}
If you're done with permissions and the rest, probably you are instantiating the BluetoothAdapter in an incorrect way. Try instantiating it like the following example in your main activity's onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnScan = (Button) findViewById(R.id.btnScan);
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mHandler = new Handler();
}
Update for test
Well I had to make a new basic project to test it. I'll share te manifest and activity code, also the output. I've tested it on my Android 11 installed mobile. Note that I've removed all scan filters.
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.karalama">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<application
android:allowBackup="true"
android:dataExtractionRules="#xml/data_extraction_rules"
android:fullBackupContent="#xml/backup_rules"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Karalama"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.Karalama.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity
package com.example.karalama;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.os.Build;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.karalama.databinding.ActivityMainBinding;
import android.view.Menu;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mBluetoothLeScanner = null;
private boolean mScanning = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
com.example.karalama.databinding.ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
Log.d("BLE", "onCreate: Bluetoothadapter null"+(mBluetoothAdapter == null));
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
binding.fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
checkBTPermission();
String[] names=new String[]{"Auden test"};
List<ScanFilter> filters=null;
if(names != null){
filters=new ArrayList<>();
for(String name:names){
ScanFilter filter=new ScanFilter.Builder().setDeviceName(name).build();
filters.add(filter);
}
}
if(mBluetoothLeScanner==null){
Log.i("BLE","could not get scanner");
}else{
mBluetoothLeScanner.startScan(mLeScanCallback);
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
private final ScanCallback mLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
//super.onScanResult(callbackType, result);
BluetoothDevice btdevice = result.getDevice();
Log.d("BLE", "Found -> "+btdevice.getAddress());
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.d("BLE", "error");
}
};
private void checkBTPermission(){
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
int pc=this.checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION");
pc+=this.checkSelfPermission("Manifest.permission.ACCESS_COARSE_LOCATION");
if(pc!=0){
this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},1001);
}else {
Log.d("BLE","checkBT permission");
}
}
}
}
The logcat output
I/RenderThread: type=1400 audit(0.0:1130983): avc: denied { ioctl } for uid=10254 path="/dev/kgsl-3d0" dev="tmpfs" ino=15422 ioctlcmd=945 scontext=u:r:untrusted_app:s0:c254,c256,c512,c768 tcontext=u:object_r:device:s0 tclass=chr_file permissive=1 app=com.example.karalama
I/RenderThread: type=1400 audit(0.0:1130984): avc: denied { read write } for uid=10254 path="/dev/kgsl-3d0" dev="tmpfs" ino=15422 scontext=u:r:untrusted_app:s0:c254,c256,c512,c768 tcontext=u:object_r:device:s0 tclass=chr_file permissive=1 app=com.example.karalama
D/BluetoothAdapter: isLeEnabled(): ON
D/BluetoothLeScanner: onScannerRegistered() - status=0 scannerId=6 mScannerId=0
D/BLE: Found -> C8:FD:19:7F:5A:7D
D/BLE: Found -> 5F:EE:47:E9:F3:A3
D/BLE: Found -> D4:36:39:B5:D3:5D
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> 40:94:41:26:D2:D6
D/BLE: Found -> D4:36:39:8A:A9:AB
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> 5F:EE:47:E9:F3:A3
D/BLE: Found -> 4B:A2:87:44:4C:79
D/BLE: Found -> D4:36:39:8A:A9:AB
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> D4:36:39:B5:D3:5D
D/BLE: Found -> 40:94:41:26:D2:D6
D/BLE: Found -> 4B:A2:87:44:4C:79
D/BLE: Found -> C8:FD:19:7F:5A:7D
D/BLE: Found -> D4:36:39:B5:D3:5D
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> 7A:0F:6B:DD:EC:FB
Update 2 - Get rid of the "getName" error
#Override
public void onScanResult(int callbackType, final ScanResult result) {
//super.onScanResult(callbackType, result);
BluetoothDevice btdevice = result.getDevice();
// The device may not have a name and getName may return null. Fix it in the following way
String name = btdevice.getName() == null ? "No name" : btdevice.getName();
Log.d("BLE", name+" - "+btdevice.getAddress());
}
You do indeed declare the permissions in your manifest file, but keep in mind that some of them are so called runtime permissions. You need to ask the user of your app for the permission at runtime. The Request app permissions site of the Android Developer Guide gives you more information on that topic.
You basically need a permissions callback to handle the users response:
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher, as an instance variable.
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted -> {
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
});
You also need to actually ask the user for the permission:
if (ContextCompat.checkSelfPermission(
CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// You can use the API that requires the permission.
performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected. In this UI,
// include a "cancel" or "no thanks" button that allows the user to
// continue using your app without granting the permission.
showInContextUI(...);
} else {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION);
}
The one permission that's most likely missing is the Location permission. This part of the Developer Guide lists the required permissions for BLE on Android < 11.
Please also use a generic BLE scanner such as nRF Connect to verify that your device actually advertises itself.
you can take help from this link
https://punchthrough.com/android-ble-guide/
if this still not works for use than u can use scan library
https://github.com/NordicSemiconductor/Android-Scanner-Compat-Library
I am making a cleaner version of a research drone android app that utilises DJI SDK/Hardware and attempting to structure the app more appropriately such that it can be expanded later. The application allows user selection of hardware (currently mock/test, DJI and ToDo) from a config pop up in the main display (class UserInterface extends AppCompatActivity). This is to allow the app to be used without registering or check perms when it is not being used for DJI. Previously I registered the DJI SDK when the app first opens, but it was my first real app and was a mess to follow its flow and didn't manage screen real estate very well.
When the DJI option is actioned from the pop up "config" window on the main interface from UI class, the UI class saves the config to a SettingsManager instance and calls the EventsManager instance with method runSettings(). The method actions the relevant SDK by try/catch{new DJI SDK class} and passes both the instances of EventsManager and UI.
The difference of this major version compared to my first version (which functions correctly) is the API key (changed package name, so new key), I am not doing the SDK registration in the first main activity and the SDK registration is not a viewable class, it only forwards strings to run on UI thread for showToast method in the UI class.
App does not return any errors.
showToast for messages in the DJI SDK class appear on the display but nothing inside the
SDKManagerCallback occurs.
Unsure of constructor in a class with extends AppCompatActivity, context methods seem unavailable when
tried with different class extensions.
I have tried creating two DJI developer keys, no difference.
Unsure of DJI class listing in Manifest as it is not an activity started by an intent with bundle but
does require context.
DJI SDK class is extracted from DJISDKDemo (I am fairly new to android app development, not sure I
could write my own version from the limited explanation on DJI dev site).
Thank you greatly for your help and any other tips appreciated.
The SDKRegistration class:
package com.research.droneapp; // mock package name for SO
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import dji.common.error.DJIError;
import dji.common.error.DJISDKError;
import dji.sdk.base.BaseComponent;
import dji.sdk.base.BaseProduct;
import dji.sdk.sdkmanager.DJISDKInitEvent;
import dji.sdk.sdkmanager.DJISDKManager;
public class SoftwareDevelopmentKitDJI extends AppCompatActivity {
private static final String TAG = "SoftwareDevelopmentKit";
public UserInterface userInterface;
public EventsManager eventsManager;
public static final String FLAG_CONNECTION_CHANGE = "dji_sdk_connection_change";
private static BaseProduct mProduct;
private Handler mHandler;
public boolean isConnected = false; // Variable for start up flag
private static final String[] REQUIRED_PERMISSION_LIST = new String[]{
Manifest.permission.VIBRATE,
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.WAKE_LOCK,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE,
};
private List<String> missingPermission = new ArrayList<>();
private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false);
private static final int REQUEST_PERMISSION_CODE = 12345;
public SoftwareDevelopmentKitDJI(UserInterface userInterface, EventsManager eventsManager) {
Log.d(TAG, "SoftwareDevelopmentKitDJI");
this.userInterface = userInterface;
this.eventsManager = eventsManager;
// Permission checked true actions SDK registration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkAndRequestPermissions();
}
// Handle DJI SDK hardware changes in background thread??
// ToDo: Receive hardware changes in EventsManager
mHandler = new Handler(Looper.getMainLooper());
}
/**
* Checks if there is any missing permissions, and
* requests runtime permission if needed.
*/
private void checkAndRequestPermissions() {
Log.d(TAG, "checkAndRequestPermissions: S");
// Check for permissions
for (String eachPermission : REQUIRED_PERMISSION_LIST) {
if (ContextCompat.checkSelfPermission(userInterface, eachPermission) !=
PackageManager.PERMISSION_GRANTED) {
missingPermission.add(eachPermission);
}
}
// Request for missing permissions
if (missingPermission.isEmpty()) {
Log.d(TAG, "notMissingPerms");
startSDKRegistration();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "missingPerms");
passToastToUI("Need permissions!");
ActivityCompat.requestPermissions(userInterface,
missingPermission.toArray(new String[missingPermission.size()]),
REQUEST_PERMISSION_CODE);
}
Log.d(TAG, "checkAndRequestPermissions: E");
}
/**
* Result of runtime permission request
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Check for granted permission and remove from missing list
if (requestCode == REQUEST_PERMISSION_CODE) {
for (int i = grantResults.length - 1; i >= 0; i--) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
missingPermission.remove(permissions[i]);
}
}
}
// If no missing permission, start SDK registration
if (missingPermission.isEmpty()) {
startSDKRegistration();
} else {
passToastToUI("Missing permissions!!!");
}
}
private void startSDKRegistration() {
Log.d(TAG, "startSDKRegistration: S");
if (isRegistrationInProgress.compareAndSet(false, true)) {
AsyncTask.execute(new Runnable() {
#Override
public void run() {
passToastToUI("registering, pls wait...");
Log.d(TAG, "startSDKRegistration: run");
// ToDO: Investigate why SDKManagerCallback's don't occur
// (is getApplicationContext() correct?)
DJISDKManager.getInstance().registerApp(getApplicationContext(),
new DJISDKManager.SDKManagerCallback() {
#Override
public void onRegister(DJIError djiError) {
Log.d(TAG, "onRegister: S");
if (djiError == DJISDKError.REGISTRATION_SUCCESS) {
passToastToUI("Register Success");
DJISDKManager.getInstance().startConnectionToProduct();
}
else {
passToastToUI("Register sdk failed!");
}
Log.v(TAG, djiError.getDescription());
Log.d(TAG, "onRegister: E");
}
#Override
public void onProductDisconnect() {
Log.d(TAG, "onProductDisconnect");
passToastToUI("Product Disconnected");
notifyStatusChange();
isConnected = false; // Set hardware connection flag
}
#Override
public void onProductConnect(BaseProduct baseProduct) {
Log.d(TAG, String.format("onProductConnect newProduct:%s", baseProduct));
passToastToUI("Product Connected");
notifyStatusChange();
isConnected = true; // Set hardware connection flag
}
#Override
public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent,
BaseComponent newComponent) {
if (newComponent != null) {
newComponent.setComponentListener(new BaseComponent.ComponentListener() {
#Override
public void onConnectivityChange(boolean isConnected) {
Log.d(TAG, "onComponentConnectivityChanged: " + isConnected);
notifyStatusChange();
}
});
}
Log.d(TAG, String.format(
"onComponentChange key:%s, oldComponent:%s, newComponent:%s",
componentKey, oldComponent, newComponent));
}
#Override
public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int i) {
Log.d(TAG, "startSDKRegistration: onInitProcess");
}
#Override
public void onDatabaseDownloadProgress(long l, long l1) {
Log.d(TAG, "startSDKRegistration: onDatabaseDownloadProgress");
}
});
}
});
}
Log.d(TAG, "startSDKRegistration: E");
}
private void notifyStatusChange() {
mHandler.removeCallbacks(updateRunnable);
mHandler.postDelayed(updateRunnable, 500);
}
private Runnable updateRunnable = () -> {
Intent intent = new Intent(FLAG_CONNECTION_CHANGE);
sendBroadcast(intent);
};
private void passToastToUI(String toastMsg) {
runOnUiThread(() -> {
userInterface.showToast(toastMsg);
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.research.droneapp"> <!-- mock package name for SO -->
<!-- Permissions and features -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true" />
<!-- App Activity Process -->
<application
android:name="com.research.droneapp.MApplication"
android:allowBackup="true"
android:icon="#mipmap/hmu_icon"
android:label="#string/app_name"
android:roundIcon="#mipmap/hmu_icon_round"
android:supportsRtl="true"
android:theme="#style/Theme.AppCompat.NoActionBar">
<!-- Main Display -->
<activity android:name="com.research.droneapp.UserInterface" />
<!-- DJI SDK -->
<uses-library android:name="com.android.future.usb.accessory" />
<meta-data
android:name="com.dji.sdk.API_KEY"
android:value="*************" /> <!-- removed for SO -->
<activity
android:name="dji.sdk.sdkmanager.DJIAoaControllerActivity"
android:theme="#android:style/Theme.Translucent">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="#xml/accessory_filter" />
</activity>
<service android:name="dji.sdk.sdkmanager.DJIGlobalService">
</service>
<activity android:name="com.research.droneapp.SoftwareDevelopmentKitDJI" />
<!-- Splash Screen at Launch -->
<activity android:name="com.research.droneapp.Splash" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The method that starts the SDK class:
/**
* Settings changes to start relevant classes
*/
public void runSettings() {
Log.d(TAG, "runSettings: S");
// Get hardware selection from settings
int hardwarePosition = settingsManager.getHardwareInt();
/*ToDo: Set Test Environment*/
if (hardwarePosition == 0) { // Operate settings for test environment
Log.d(TAG, "runSettings: hardPos Test");
userInterface.showToast("runSetting: TEST");
}
else if (hardwarePosition == 1) { // Operate settings for DJI
Log.d(TAG, "runSettings: hardPos MavP2");
try {
this.softwareDevelopmentKitDJI = new SoftwareDevelopmentKitDJI(
userInterface, this);
Log.d(TAG, "runSettings: DJI Launched");
userInterface.showToast("runSetting: DJI");
} catch (Exception e) {
Log.d(TAG, "runSettings: DJI Error: "+ e.toString());
userInterface.showToast("runSetting: Error");
}
} // Operate settings for...?
else if (hardwarePosition == 2) { /*ToDo*/
Log.d(TAG, "runSettings: hardPos ToDo");
userInterface.showToast("runSetting: ToDo");
}
else { // Unknown hardware
Log.d(TAG, "runSettings: Error");
userInterface.showToast("runSetting:Error");
}
Log.d(TAG, "runSettings: E");
}
The config window click listener for action changes button, within the method of UI class for config pop up window:
// Action changes
Button btnAction = popupView.findViewById(R.id.btnAction);
btnAction.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "displayConfigOverlay: onClick -> configAction");
// Update hardware setting
String hardware =
(btnTest.isChecked())?settingsManager.HARDWARE_OPTIONS[0]:
(btnMavP2.isChecked())?settingsManager.HARDWARE_OPTIONS[1]:
(btnToDo.isChecked())?settingsManager.HARDWARE_OPTIONS[2]:
"NoSelection!";
settingsManager.setHardware(hardware);
// Update port number
String port = serverPortEdit.getText().toString();
settingsManager.setServerPort(port);
// Update WSS launch setting
boolean autoLunchWSS = swAutoWSS.isChecked();
settingsManager.setAutoLaunchWSS(autoLunchWSS);
// Display to user
showToast("Hardware: " + hardware + "\nPort: " + port + "\nAutoWSS: " +
autoLunchWSS);
// Push settings
eventsManager.runSettings();
// Close config pop up
popupConfig.dismiss();
}
});
Apologises for the lack of and mixed commenting styles. Again any tips appreciated, still new to android and java.
Min API23 setting in gradle allowed code to run but did not allow SDK to download. Unsure why errors not returned.
Solved by changing gradle to following:
...
android {
compileSdkVersion 29
buildToolsVersion '28.0.3'
useLibrary 'org.apache.http.legacy'
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
multiDexEnabled true
ndk {
abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
Additionally found a potential context issue within layout, tools:context="..." was not set to the Activity interacting with this layout.
...
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".UserInterface" >
...
I understand this questions has been asked many times, for each question that has already been asked, the implementation is different from mine. The method I used create and call the intentService for a geocoder, was used from the android dev tutorial site.
https://developer.android.com/training/location/display-address.html.
I have gone over the the tutorial 3 times over to make sure I wasn't missing anything, but it is clear that I am.
Here is my manifest file, I have added the location and internet permissions.
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".selectRouteAndTransportMethod">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<service
android:name=".FetchAddressIntentService"
android:exported="false"/>
</activity>
<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key" />
<activity
android:name="user.com.commuterapp.MapsActivity"
android:label="#string/title_activity_maps"></activity><!-- ATTENTION: This was auto-generated to add Google Play services to your project for
App Indexing. See https://g.co/AppIndexing/AndroidStudio for more information. -->
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
Here is my main activity: All I am doing here is using a buttonClick to create the intent and to start the intent.
package user.com.commuterapp;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;
import user.com.commuterapp.R;
import static java.lang.Boolean.TRUE;
public class selectRouteAndTransportMethod extends AppCompatActivity {
Intent mIntent;
PendingIntent mPendingIntent;
myLocation mCurrentLocation;
private AddressResultReceiver mResultReceiver;
public static final String TAG = selectRouteAndTransportMethod.class.getSimpleName();
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_route_and_transport_method);
mIntent = new Intent(this, selectRouteAndTransportMethod.class);
mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);
mCurrentLocation = new myLocation(this, this, mPendingIntent);
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
}
#Override
protected void onStart() {
super.onStart();
Log.d(TAG, "On Start");
mCurrentLocation.connect();
boolean connecting = mCurrentLocation.mGoogleApiClient.isConnecting();
boolean registered = mCurrentLocation.mGoogleApiClient.isConnectionCallbacksRegistered(mCurrentLocation);
//ConnectionResult connectionResult = mCurrentLocation.mGoogleApiClient.getConnectionResult(LocationServices.API);
if (connecting == TRUE) {
Log.d(TAG, "Connecting");
}
if (registered == TRUE) {
Log.d(TAG, "registered");
}
}
#Override
protected void onResume() {
super.onResume();
mCurrentLocation.connect();
}
#Override
protected void onPause() {
super.onPause();
// mCurrentLocation.disconnect();
}
protected void buttonOriginClick(View view)
{
Log.d(TAG,"retrieving address");
retrieveAddress();
}
protected void retrieveAddress() {
Intent geoCoderIntent = new Intent(this, FetchAddressIntentService.class);
geoCoderIntent.putExtra(FetchAddressIntentService.Constants.RECIEVER, mResultReceiver);
geoCoderIntent.putExtra(FetchAddressIntentService.Constants.LOCATION_DATA_EXTRA, mCurrentLocation.mCurrentLocation);
startService(geoCoderIntent);
}
}
And lastly here is the intentService class. This was straight out of the android dev tutorial site, nothing original here.
package user.com.commuterapp;
import android.app.IntentService;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static android.content.ContentValues.TAG;
/**
* Created by User on 1/21/2017.
*/
public class FetchAddressIntentService extends IntentService {
public final class Constants{
public static final int SUCCESS_RESULT = 0;
public static final int FAILURE_RESULT = 1;
public static final String PACKAGE_NAME =
"user.com.commuterapp";
public static final String RECIEVER = PACKAGE_NAME +
".RECIEVER";
public static final String RESULT_DATA_KEY = PACKAGE_NAME + ".RESULT_DATA_KEY";
public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME +
".LOCATION_DATA_EXTRA";
}
protected ResultReceiver mReceiver;
public FetchAddressIntentService()
{
super("FetchAddressIntentService");
}
private void deliverResultToReceiver(int resultCode, String message){
Bundle bundle = new Bundle();
bundle.putString(Constants.RESULT_DATA_KEY, message);
mReceiver.send(resultCode,bundle);
}
#Override
protected void onHandleIntent (Intent intent) {
Log.d(TAG, "GeoCoder Service Started");
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
String errorMessage = "";
Location location = intent.getParcelableExtra(Constants.LOCATION_DATA_EXTRA);
List<Address> addresses = null;
try {
addresses = geocoder.getFromLocation(
location.getLongitude(),
location.getLatitude(),
1);
}catch (IOException ioException) {
//catch network or other I/O Problems
errorMessage = getString(R.string.service_not_available);
Log.e(TAG, errorMessage);
} catch (IllegalArgumentException illegalArgumentException){
//catch invalid latitude or longitude values
errorMessage = getString(R.string.invalid_lat_long_used);
Log.e(TAG, errorMessage + "." + "Latitude =" + location.getLatitude()
+ ", Longitude = " + location.getLongitude(),
illegalArgumentException);
}
//handle cases where no addresses was foudn
if (addresses == null || addresses.size() == 0)
{
if(errorMessage.isEmpty()){
errorMessage = getString(R.string.no_address_found);
Log.e(TAG,errorMessage);
}
deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
}else
{
Address address = addresses.get(0);
ArrayList<String> addressFragments = new ArrayList<String>();
//Fetch the address lines using getAddressLine
//join them and send them to the thread
for(int i = 0; i < address.getMaxAddressLineIndex(); i++){
addressFragments.add(address.getAddressLine(i));
}
Log.i(TAG, getString(R.string.address_found));
deliverResultToReceiver(Constants.SUCCESS_RESULT,
TextUtils.join(System.getProperty("line.seperator"), addressFragments));
}
}
}
EDIT: I have tried rewriting the geocoder following the instructions from the android dev site again, but no success.
EDIT2: I noticed in the manifest I declared the service, within the application declaration, instead of outside, I made the change but to no avail, any insights will be appreciated
I am writing an GCM support for Android 5.0.1. The app registers fine and sending a message to the server also seems ok, however the onReceive method on my BroadcastReceiver doesn't get fired.
But I am able to see my gsm message id in the log cat.
Here is the manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.freshmanapp.gcmtest">
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="8" />
<permission
android:name="com.freshmanapp.gcmtest.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.freshmanapp.gcmtest.permission.C2D_MESSAGE" />
<application
android:allowBackup="true"
android:label="#string/app_name"
android:icon="#mipmap/ic_launcher"
android:theme="#style/AppTheme">
<activity
android:name=".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>
</application>
<meta-data
android:name="com.google.android.gms.version"
android:value="6" />
<receiver
android:name="com.freshmanapp.gcmtest.GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.freshmanapp.gcmtest" />
</intent-filter>
</receiver>
<service android:name="com.freshmanapp.gcmtest.GcmIntentService" />
</manifest>
and here is my GcmBroadcastReceiver.java
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("GCM Reciever","Triggered");
// Explicitly specify that GcmIntentService will handle the intent.
ComponentName comp = new ComponentName(context.getPackageName(),
GcmIntentService.class.getName());
// Start the service, keeping the device awake while it is launching.
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
MainActivity.java
package com.freshmanapp.gcmtest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by Ramkumar on 16/04/15.
*/
public class MainActivity extends Activity {
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
public static final String EXTRA_MESSAGE = "message";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private static final String TAG = "GCMRelated";
GoogleCloudMessaging gcm;
AtomicInteger msgId = new AtomicInteger();
String regid;
#Override
protected void onCreate(Bundle savedInstanceState) {
startService(new Intent(this, GcmIntentService.class));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(getApplicationContext());
regid = getRegistrationId(getApplicationContext());
Log.i("Registered Id ",regid);
if (regid.isEmpty()) {
new RegisterApp(getApplicationContext(), gcm, getAppVersion(getApplicationContext())).execute();
Toast.makeText(getApplicationContext(), "Device Registered Now", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(getApplicationContext(), "Device already Registered ("+regid+")", Toast.LENGTH_SHORT).show();
}
}
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(TAG, "This device is not supported.");
finish();
}
return false;
}
return true;
}
/**
* Gets the current registration ID for application on GCM service.
* <p>
* If result is empty, the app needs to register.
*
* #return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(getApplicationContext());
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
/**
* #return Application's {#code SharedPreferences}.
*/
private SharedPreferences getGCMPreferences(Context context) {
// This sample app persists the registration ID in shared preferences, but
// how you store the regID in your app is up to you.
return getSharedPreferences(MainActivity.class.getSimpleName(),
Context.MODE_PRIVATE);
}
/**
* #return Application's version code from the {#code PackageManager}.
*/
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
}
Are you calling registerReceiver in your Activity?
You will need to call something like registerReceiver(new GcmBroadcastReceiver(), new IntentFilter(DISPLAY_MESSAGE_ACTION));
I want to write a UPnP control point application for Android using the CyberGarage "CyberLink for Java" API. To test the API I implemented a very simple application. In this application a UPnP control point actively searches for any UPnP root devices, listens for responses and device notifications, and prints a list of the devices, which are available on the network.
The app runs on an Android phone, but none of the UPnP devices on my network are found. I tried this on two different Android phones. To check out if this is an Android specific problem, I implemented the same functionality as a Java console application. Interestingly enough the Java console application works absolutely fine and always shows all of the UPnP devices on my network!
So why does this not work on Android? Note, in the Android app I had to implement the network specific functionality on a seperate thread using AsyncTask. Otherwise I get errors, because I am not supposed to run this on the UI thread. But this should not be the problem, am I right?
Below the source code of the two applications.
Android application:
MainActivity.java
package com.example.controller_v1;
import org.cybergarage.upnp.DeviceList;
import org.cybergarage.upnp.UPnP;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UPnP.setEnable(UPnP.USE_ONLY_IPV4_ADDR);
new StartControlPointTask().execute();
}
private class StartControlPointTask extends AsyncTask {
public static final String TAG = "StartControlPointTask";
#Override
protected Object doInBackground(Object... params) {
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
MyControlPoint controlPoint = new MyControlPoint();
controlPoint.start();
// controlPoint.search();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
DeviceList rootDevices = controlPoint.getDeviceList();
int numDevices = rootDevices.size();
if (numDevices > 0) {
for (int i = 0; i < numDevices; i++) {
Log.i(TAG, "device " + i + ": " + rootDevices.getDevice(i).getFriendlyName());
}
} else {
Log.i(TAG, "no root devices found");//
}
return null;
}
}
}
MyControlPoint.java
package com.example.controller_v1;
import org.cybergarage.upnp.ControlPoint;
import org.cybergarage.upnp.device.NotifyListener;
import org.cybergarage.upnp.device.SearchResponseListener;
import org.cybergarage.upnp.ssdp.SSDPPacket;
import android.util.Log;
public class MyControlPoint extends ControlPoint implements NotifyListener, SearchResponseListener {
public MyControlPoint() {
addNotifyListener(this);
addSearchResponseListener(this);
}
#Override
public void deviceNotifyReceived(SSDPPacket ssdpPacket) { // NotifyListener
final String TAG = "deviceNotifyReceived";
Log.i(TAG, "executed");
}
#Override
public void deviceSearchResponseReceived(SSDPPacket ssdpPacket) { // SearchResponseListener
final String TAG = "deviceSearchResponseReceived";
Log.i(TAG, "executed");
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.controller_v1"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name="com.example.controller_v1.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>
</application>
</manifest>
Java console application:
Main.java
import org.cybergarage.upnp.DeviceList;
import org.cybergarage.upnp.UPnP;
public class Main {
public Main() {
UPnP.setEnable(UPnP.USE_ONLY_IPV4_ADDR);
MyControlPoint controlPoint = new MyControlPoint();
controlPoint.start();
// controlPoint.search();
try {
Thread.sleep(5000); // wait for devices to be found
} catch (InterruptedException e) {
e.printStackTrace();
}
DeviceList rootDevices = controlPoint.getDeviceList();
int numDevices = rootDevices.size();
if (numDevices > 0) {
for (int i = 0; i < numDevices; i++) {
System.out.println("found device " + i + ": " + rootDevices.getDevice(i).getFriendlyName());
}
} else {
System.out.println("no root devices found");
}
}
public static void main(String[] args) {
new Main();
}
}
MyControlPoint.java
import org.cybergarage.upnp.ControlPoint;
import org.cybergarage.upnp.device.NotifyListener;
import org.cybergarage.upnp.device.SearchResponseListener;
import org.cybergarage.upnp.ssdp.SSDPPacket;
public class MyControlPoint extends ControlPoint implements /*DeviceChangeListener,*/ NotifyListener, SearchResponseListener {
public MyControlPoint() {
addNotifyListener(this);
addSearchResponseListener(this);
}
#Override
public void deviceNotifyReceived(SSDPPacket packet) { // NotifyListener
System.out.println("deviceNotifyReceived");
}
#Override
public void deviceSearchResponseReceived(SSDPPacket packet) { // SearchResponseListener
System.out.println("deviceSearchReceived");
}
}
I don't have any idea, why the java console application works and the android application doesn't. I don't get any answer from the documentation. Can anybody help me?
I lifted your code, compiled it using Eclipse ADT and it ran perfectly on a virtual machine (VMWare player) running Android 4.0 (eeepc).
There is an Android section on CyberGarage website that does say that CyberLink doesn't run on the emulator yet as it doesn't support multicast yet, perhaps that is/was your problem.
Hope not too late to help.
Andrew
In my case it is displaying all the devices, may be because I'm checking with Phone rather emulator.