I am using the PayPal mobile SDK (Android) because I need access to the REST API, and I am using the Sandbox. Immediate payments work fine, but I can't get an authorization code for future payments. I have checked the developer portal and future payments are enabled, although I think this is the default for the Sandbox anyway. I am using Xamarin android (c#):
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="ShoezApp.Android">
<!--This will default to ShoezApp.ShoezApp if no second part is specified-->
<uses-sdk android:minSdkVersion="19" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- admob permissions-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application android:label="ShoezApp" android:icon="#drawable/icon">
<activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity" android:launchMode="singleTop" android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="shoezapp" android:host="easyauth.callback" />
</intent-filter>
</activity>
<meta-data android:name="com.google.android.gms.version" android:value="#integer/google_play_services_version"/>
<activity android:name="com.google.android.gms.ads.AdActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android:theme="#android:style/Theme.Translucent" />
</application>
</manifest>
Code:
public class PayPalDroid : IPayPalService
{
private PayPalConfiguration config = new PayPalConfiguration()
.Environment(PayPalConfiguration.EnvironmentSandbox)
.ClientId("AeX06w97L702sEFGK5ZB4Tc4Veyo4oOzopS9DgKiHCDVgD4O0mGeZoNl1t-sFWAmRXyzyx87Y1mupJ1W")
.MerchantName("Example Merchant")
.MerchantPrivacyPolicyUri(Android.Net.Uri.Parse("https://www.example.com/privacy"))
.MerchantUserAgreementUri(Android.Net.Uri.Parse("https://www.example.com/legal"));
//only calls default constructor with dependency injection, so this is necessary
public PayPalDroid() {}
private int REQUEST_CODE_PAYMENT = 1;
private int REQUEST_CODE_FUTURE_PAYMENT = 2;
private int REQUEST_CODE_PROFILE_SHARING = 3;
public Task<string> RequestPayPal()
{
Context context = MainActivity.Instance;
MainActivity activity = (MainActivity)context;
//create the listener
var listener = new ActivityResultListener(activity);
// start paypal service - this must be done each time
var intent = new Intent(context, typeof(PayPalService));
intent.PutExtra(PayPalService.ExtraPaypalConfiguration, config);
context.StartActivity(Intent.CreateChooser(intent, "Request PayPal transaction"));
var payment = new PayPalPayment(new Java.Math.BigDecimal("2.45"), "USD", "the item",
PayPalPayment.PaymentIntentSale);
intent = new Intent(context, typeof(PaymentActivity));
intent.PutExtra(PayPalService.ExtraPaypalConfiguration, config);
intent.PutExtra(PaymentActivity.ExtraPayment, payment);
activity.StartActivityForResult(Intent.CreateChooser(intent, "PayPal transaction complete"), REQUEST_CODE_FUTURE_PAYMENT);
return listener.Task;
}
private class ActivityResultListener
{
private TaskCompletionSource<string> Complete = new TaskCompletionSource<string>();
public Task<string> Task { get { return this.Complete.Task; } }
MainActivity Activity;
public ActivityResultListener(MainActivity activity)
{
Activity = activity;
// subscribe to activity results
activity.ActivityResult += OnActivityResult;
}
private void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
// unsubscribe from activity results
Activity.ActivityResult -= OnActivityResult;
// process result
if (resultCode == Result.Ok)
{
Object auth = data.GetParcelableExtra(PayPalFuturePaymentActivity.ExtraResultAuthorization);
if (auth != null) //**THIS IS ALWAYS NULL**//
{
try
{
//string authorization_code = auth.AuthorizationCode;
//sendAuthorizationToServer(auth);
}
catch (Exception e)
{
//Log.e("FuturePaymentExample", "an extremely unlikely failure occurred: ", e);
}
}
}
Complete.TrySetResult(resultCode.ToString());
Context context = MainActivity.Instance;
var intent = new Intent(context, typeof(PayPalService));
context.StopService(intent);
}
}
}
I can't go any further (i.e. server side coding) without an authorization code. The problem is the 'auth' object is always null, and I can't see anything wrong with the code as it it basically the sample code. What am I missing? Or does this only work in the live environment?
Thanks.
Made a simple error, the activity needs to be a PayPalFuturePaymentActivity, not a PaymentActivity. A PaymentConfirmation object is available for a PaymentActivity, not a PayPalAuthorization object.
I have found some really good example code for PayPal integration here, which may be helpful to someone else: https://www.csharpcodi.com/vs2/?source=176/PayPal.Forms/MobileApps/PayPal.Forms/PayPalAndroidTest/PayPalManager.cs
Related
I need help or guidance in resolving the issue that I have regarding React Native interacting with other native applications via intents. I know that React Native supports deeplinking out of the box but does not cater for intents, which means that one needs to create an android native module (https://reactnative.dev/docs/native-modules-android). I created a native module that calls 3rd party applications via intents and passing data using startActivityForResults and created onActivityResult that is supposed to handle data returned from 3rd party applications after closing. However, the onActivityResult executes prematurely before external applications open.
Starting the activity using startActivityForResults:
#ReactMethod
public void launchApp(String stringArgument, ReadableMap args, Promise promise) throws JSONException{
try {
final JSONObject options = convertMapToJson(args);
Bundle extras = new Bundle();;
int LAUNCH_REQUEST = 0;
if (options.has("extras")) {
extras = createExtras(options.getJSONArray("extras"));
Log.d(TAG,"Extras found");
Log.d(TAG, options.getString("extras"));
} else {
extras = new Bundle();
Log.d(TAG,"No extras");
}
if (options.has("launchRequestCode")) {
LAUNCH_REQUEST = options.getInt("launchRequestCode");
}
Intent packageIntent = this.reactContext.getPackageManager().getLaunchIntentForPackage(stringArgument);
if(packageIntent != null){
packageIntent.putExtras(extras);
//callback.invoke("Starting activity for: "+stringArgument);
Activity activity = getReactApplicationContext().getCurrentActivity();
//this.reactContext.startActivityForResult(packageIntent, LAUNCH_REQUEST, extras);
activity.startActivityForResult(packageIntent, LAUNCH_REQUEST);
return;
//mPromise.put(LAUNCH_REQUEST, promise);
}
else{
Log.d(TAG, stringArgument+" package not found");
//callback.invoke("Package not found: "+stringArgument);
}
} catch (JSONException e) {
//TODO: handle exception
Log.d(TAG, e.toString());
}
// TODO: Implement some actually useful functionality
}
Expecting the data back using onActivityResults
ActivityEventListener mActivityEventListener = new ActivityEventListener(){
#Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
//super.onActivityResult(requestCode, resCode, data);
Log.d(TAG,"On activity result");
if (mPromise != null && resultCode == activity.RESULT_OK) {
WritableMap result = new WritableNativeMap();
result.putInt("resultCode", resultCode);
result.putMap("data", Arguments.makeNativeMap(data.getExtras()));
mPromise.resolve(result);
}
else{
Log.d(TAG,"Promise and intent data are empty");
//mPromise.reject("Unable to get promise or intent data is empty");
}
if(resultCode == activity.RESULT_CANCELED ){
Log.d(TAG,"Result cancelled or no result or crashed with code");
}
}
#Override
public void onNewIntent(Intent intent){
Log.d(TAG,"New Intent");
}
};
Android Manifest file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.navapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:label="#string/app_name"
android:icon="#mipmap/ic_launcher"
android:roundIcon="#mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
By using Android Studio Logcat to debug, I found out that the onActivityResults executes immediately before the external app opens up.
Logcat screenshot
Thanks to this explanation here, I was able to drill down to what was causing the issue. In my React Native application, I used the package name of the external application instead of the action name to open it.
Manifest File of the external app:
<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/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.bld.pushnotification.Main"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
I also modified the my code for launching the app:
#ReactMethod
public void launchApp(String stringArgument, ReadableMap args, Promise promise) throws JSONException{
try {
final JSONObject options = convertMapToJson(args);
Bundle extras = new Bundle();;
int LAUNCH_REQUEST = 0;
if (options.has("extras")) {
extras = createExtras(options.getJSONArray("extras"));
Log.d(TAG,"Extras found");
Log.d(TAG, options.getString("extras"));
} else {
extras = new Bundle();
Log.d(TAG,"No extras");
}
if (options.has("launchRequestCode")) {
LAUNCH_REQUEST = options.getInt("launchRequestCode");
}
Intent packageIntent = new Intent(stringArgument);
Activity activity = getReactApplicationContext().getCurrentActivity();
activity.startActivityForResult(packageIntent, LAUNCH_REQUEST);
} catch (JSONException e) {
//TODO: handle exception
Log.d(TAG, e.toString());
}
}
Today I got a rejection from Google for my app Facetocall
Your app does not appear to prompt the user to be a default handler prior to requesting related permissions as required by the policy.
Please make necessary changes in order to comply with policy
requirements and resubmit your app through a Declaration Form.
Default handler capability was listed on your declaration form, but your app has no default handler capability.
My goal is to make a default dialer app.
Here is my Manifest
<?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.gazman.beep"
android:installLocation="preferExternal">
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.SEND_SMS" />
... and other permissions
<application
android:name=".application.BeepApp"
android:allowBackup="false"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".system_intents.IntentsActivity"
android:launchMode="singleTask"
android:noHistory="true"
android:theme="#style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
</activity>
<activity
android:name=".call.CallActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:showForAllUsers="true" />
<service
android:name="com.gazman.beep.call.MyInCallService"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>
... And other declarations
</application>
</manifest>
And here is what I do when my app launches:
private void checkDefaultHandler() {
if (isAlreadyDefaultDialer()) {
return;
}
Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName());
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER);
}
else{
throw new RuntimeException("Default phone functionality not found");
}
}
private boolean isAlreadyDefaultDialer() {
TelecomManager telecomManager = (TelecomManager) getSystemService(TELECOM_SERVICE);
return getPackageName().equals(telecomManager.getDefaultDialerPackage());
}
What am I missing here?
I tried submitting the form again and this time I add a video that shows my app on an emulator(I see the same behavior on all the real devices too) here is the reply that I got back:
Your app does not appear to prompt the user to be a default handler prior to requesting related permissions as required by the policy.
Please make necessary changes in order to comply with policy
requirements and resubmit your app through a Declaration Form.
to make default dialer app, you need to do 2 things :
1. add the following permissions in your android manifest
<activity>
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
actually performing the check :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
...
checkDefaultDialer()
...
}
const val REQUEST_CODE_SET_DEFAULT_DIALER=200
private fun checkDefaultDialer() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
val isAlreadyDefaultDialer = packageName == telecomManager.defaultDialerPackage
if (isAlreadyDefaultDialer)
return
val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CODE_SET_DEFAULT_DIALER -> checkSetDefaultDialerResult(resultCode)
}
}
private fun checkSetDefaultDialerResult(resultCode: Int) {
val message = when (resultCode) {
RESULT_OK -> "User accepted request to become default dialer"
RESULT_CANCELED -> "User declined request to become default dialer"
else -> "Unexpected result code $resultCode"
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
You don't need to panic. This kind of conversation happened with me also, regarding AdMob content on my app. I declared everything perfectly but still they were saying content rating not fine due to type of Ads my app was showing. When more mails exchanged they sent me screenshot with the proof of wrong Ads, so finally i checked my whole code again and found my mistake.
The point here is that Google is good at what they do, and if they said so, then your app lacks something.
To be very honest, your app did not ask the user anywhere to allow it to be set as default, instead it set itself default in the background. You should ask for every permission required by your app that are critical and can be used by any app or virus or spyware to interfere with user privacy.
You can do that with a function like in the following example, which is asking for Camera permission from the user:
private void requestCameraPermission() {
Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission.");
// BEGIN_INCLUDE(camera_permission_request)
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// For example if the user has previously denied the permission.
Log.i(TAG,
"Displaying camera permission rationale to provide additional context.");
Snackbar.make(mLayout, R.string.permission_camera_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
#Override
public void onClick(View view) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA);
}
})
.show();
} else {
// Camera permission has not been granted yet. Request it directly.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA);
}
// END_INCLUDE(camera_permission_request)
}
You can see the complete repository at Google Samples
And don't worry. If you rectify this problem, they will accept your application, as they did for mine.
in case anyone comes cross this post . . .
I used this for asking the user for changing the default dailer.
Know that the there gonna be 2 windows prompting(for me it was fine).
private void setDefaultDialer()
{
AlertDialog.Builder builder;
builder = new AlertDialog.Builder(this);
builder.setMessage("Do you want to make Cricket your default Dialer?(it will not cover or replace your dialer)")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
defaultDialerPackage = "cricket";
Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
startActivityForResult(intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,getPackageName()),REQUEST_CODE_SET_DEFAULT_DIALER);
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
Toast.makeText(getApplicationContext(),"Cancelled - No action was taken",
Toast.LENGTH_SHORT).show();
}
});
AlertDialog alert = builder.create();
alert.setTitle("Cricket need default dialer permission!!");
alert.show();
}
Preconditions
1. App starts with LinkActivity, at this point we have no deep link intent, it's ok.
Main activity launched. There we are able to click the deep link.
By clicking on deep link opens LinkActivity, uri is correct, referringParams json is not empty (ok). But...
When we replaying step 2: uri is correct, but the reffering params are empty: "{}"; All other tries are with the same result.
Only when we pausing the app (for example switching to the recent apps menu) and then returning to the app - deep link works as expected, but only at first try. May be some issues with the session close (but in the current version of the sdk it self controls session close)
public class LinkActivity extends AppCompatActivity {
private static final String TAG = LinkActivity.class.getSimpleName();
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onStart() {
super.onStart();
Uri uri = getIntent().getData();
Log.w(TAG, "uri: " + uri);
Branch.getInstance().initSession(new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
Log.w(TAG, "json: " + referringParams);
startActivity(new Intent(LinkActivity.this, MainActivity.class));
}
}, uri, this);
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
public class BranchApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Branch.enableLogging();
Branch.getAutoInstance(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.myapp">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".BranchApplication"
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/AppTheme">
<activity android:name=".LinkActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="myapp.link"
android:scheme="https" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"/>
<meta-data
android:name="io.branch.sdk.BranchKey"
android:value="#string/branch_io_live_key" />
<meta-data
android:name="io.branch.sdk.BranchKey.test"
android:value="#string/branch_io_test_key" />
<meta-data
android:name="io.branch.sdk.TestMode"
android:value="false" />
</application>
</manifest>
implementation "io.branch.sdk.android:library:2.14.3"
Update:
Even with android:launchMode="singleInstance" for LinkActivity steel reproduces (I don't think this is the case).
Udpate2:
Bhardwaj mentioned that no need to call initSession when we initing Branch via getAutoInstance. But how to get refferingParams from uri in that case?
Update3:
From the Branch.checkIntentForSessionRestart doc:
Check for forced session restart. The Branch session is restarted if
the incoming intent has branch_force_new_session set to true. This is
for supporting opening a deep link path while app is already running
in the foreground. Such as clicking push notification while app in
foreground.
So, My desired behavior is matches this description. But how to force session restart?
You can try as mentioned below :-
Branch.getAutoInstance(this) -> Branch.getAutoInstance(this, true)
Branch.getInstance(context) -> Branch.getInstance()
Do not call initSession when you have getAutoInstance()
if(!initiatedBranchDeepLinks) {
// Configure Branch.io
initiatedBranchDeepLinks = true;
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
String message = "Branch.io onInitFinished. Params: " + referringParams.toString();
Log.d(TAG, message);
} else {
Log.i(TAG, error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
Here is Branch Test Bed app:
https://github.com/BranchMetrics/android-branch-deep-linking/tree/master/Branch-SDK-TestBed
You can use this as a reference and see what you are doing incorrectly.
This could be caused by your Manifest configuration. In your <activity> tag, you should include android:launchMode="singleTask". See this section of our docs. This may explain why you are receiving the parameters the first time, but not receiving them on a re-open.
I'm trying to debugging a service in Android.
I've already put the line Debug.waitForDebugger(); in this method:
#Override
protected void onHandleIntent(Intent intent)
{
Debug.waitForDebugger();
Utilities.displayAlertDialog("on handle intent", this);
SharedPreferences sp = getSharedPreferences(getString(clyky.cartracker.R.string.sharedPreferencesName), Context.MODE_PRIVATE);
int userID = sp.getInt("id_user", SplashActivity.DEFAULT_USER_ID);
if (userID != SplashActivity.DEFAULT_USER_ID)
{
sendRequest(userID);
}
else
{
stopSelf();
}
}
I've put a breakpoint on line 2 (Utilities.displayAlertDialog("on handle intent", this);, but this breakpoint is never reached.
I'm debugging my app on my device and I'm using Android Studio.
EDIT
This is my entire service class. It retrieves some information from the database and puts all into an ArrayList:
public class RetrieveVehicleListService extends IntentService
{
private static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder builder;
private ArrayList<Vehicle> vehicles;
private void parseVehiclesFromMap(ArrayList vehicles)
{
for (int i = 0; i < vehicles.size(); i++)
{
final Vehicle v = new Vehicle();
HashMap vehicleMap = (HashMap) vehicles.get(i);
v.setPlate(vehicleMap.get("plate").toString());
v.setKm(vehicleMap.get("km") == null ? null : Integer.parseInt(vehicleMap.get("km").toString()));
v.setFuelQuantity(Double.parseDouble(vehicleMap.get("fuel_quantity").toString()));
v.setEffectiveFuelEconomy(Double.parseDouble(vehicleMap.get("fuel_economy").toString()));
v.setInsuranceDate(vehicleMap.get("insurance_date") == null ? null : new LocalDate(vehicleMap.get("insurance_date").toString()));
v.setMatriculationDate(new LocalDate(vehicleMap.get("matriculation_date").toString()));
v.setLatitude(vehicleMap.get("latitude") == null ? null : Double.parseDouble(vehicleMap.get("latitude").toString()));
v.setLongitude(vehicleMap.get("longitude") == null ? null : Double.parseDouble(vehicleMap.get("longitude").toString()));
v.setFuelType(FuelType.fromInt(Integer.parseInt(vehicleMap.get("id_fuel").toString())));
this.vehicles.add(v);
}
}
private void sendRequest(int userID)
{
Response.Listener<String> listener = new Response.Listener<String>()
{
#Override
public void onResponse(String response)
{
try
{
HashMap json = new ObjectMapper().readValue(response, HashMap.class);
String errorCode = json.get("error_code").toString();
switch (errorCode)
{
case "0":
parseVehiclesFromMap((ArrayList) json.get("vehicles"));
break;
default:
// TODO gestire
break;
}
}
catch (IOException e)
{
// TODO gestire
e.printStackTrace();
}
}
};
VehicleListRequest request = new VehicleListRequest(String.valueOf(userID), listener, null);
RequestQueue queue = Volley.newRequestQueue(this);
queue.add(request);
}
#Override
protected void onHandleIntent(Intent intent)
{
Debug.waitForDebugger();
Utilities.displayAlertDialog("on handle intent", this);
SharedPreferences sp = getSharedPreferences(getString(clyky.cartracker.R.string.sharedPreferencesName), Context.MODE_PRIVATE);
int userID = sp.getInt("id_user", SplashActivity.DEFAULT_USER_ID);
if (userID != SplashActivity.DEFAULT_USER_ID)
{
sendRequest(userID);
}
else
{
stopSelf();
}
}
public RetrieveVehicleListService()
{
super("RetrieveVehicleList");
vehicles = new ArrayList<>();
}
}
This is my BroadcastReceiver, which runs my RetrieveVehiclesListService when the internet connection is available:
public class NetworkWatcher extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
Intent retrieveVehicleList = new Intent(context, RetrieveVehicleListService.class);
if (info != null)
{
if (info.isConnected())
{
if (!Utilities.vehicleFileExists(context))
{
context.startService(retrieveVehicleList);
}
}
else
{
context.stopService(retrieveVehicleList);
}
}
}
}
this is my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="clyky.cartracker">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--
TODO crediti per le icone:
<div>Icons made by Dave Gandy from www.flaticon.com is licensed by CC 3.0 BY</div>
per la freccia in giĆ¹ che espande il layout nella LoginActivity
<div>Icons made by Dave Gandy from www.flaticon.com is licensed by CC 3.0 BY</div>
per la freccia in su
<div>Icons made by Madebyoliver from www.flaticon.com is licensed by CC 3.0 BY</div>
per il lucchetto
Car rental icon credits
per l'icona della macchina
-->
<application
android:name=".AppGlobal"
android:allowBackup="true"
android:icon="#mipmap/ic_app_icon"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".activities.RegistrationActivity">
</activity>
<activity android:name=".activities.LoginActivity">
</activity>
<activity android:name=".activities.VehicleListActivity">
</activity>
<activity android:name=".activities.VehicleDetailsActivity">
</activity>
<activity android:name=".activities.AddVehicleActivity">
</activity>
<!--
The API key for Google Maps-based APIs is defined as a string resource
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=".activities.SplashActivity"
android:theme="#style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".activities.MainActivity">
</activity>
<activity android:name=".activities.DrivingLicenseActivity">
</activity>
<receiver
android:name=".services.NetworkWatcher"
android:enabled="true"
android:exported="false">
</receiver>
<service
android:name=".services.RetrieveVehicleListService"
android:exported="false">
</service>
</application>
</manifest>
Connectivity Change Action:
If you want to detect change network connection use:
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
If you want to detect WiFi state Change use:
<action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
Change you receive as per below:
<receiver android:name=".services.NetworkWatcher">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
You should completely remove the <receiver> tag from your AndroidManifest.xml because starting with Nougat (Android 7 API 24) you will not be able to receive connectivity change events in a receiver as stated here, because of new limitations to background services
You could be able to listen to CONNECTIVITY_ACTION only on your Main UI thread while the app is running and in foreground with a programmatically registered BroadCastReceiver, and this could be a good or a bad solution based on your use case.
What i suggest you, it's to read this answer: https://stackoverflow.com/a/39880860/2910520 and choose between one or a combination of the provided solutions:
JobScheduler (API 21 onwards)
GcmNetworkManager (for this to work users need Google Play Services installed)
Evernote has released a library which already switch to the best implementation available for the device, i would suggest you to use this without doing everything from scratch:
https://github.com/evernote/android-job
I'm trying to implement complication support for my watch. Here's my AndroidManifest.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.wearapp">
<uses-feature android:name="android.hardware.type.watch"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#android:style/Theme.DeviceDefault">
<!-- Watch Face -->
<service
android:name=".ComplicationSimpleWatchFaceService"
android:enabled="true"
android:label="Fancy Watch"
android:permission="android.permission.BIND_WALLPAPER">
<meta-data
android:name="android.service.wallpaper"
android:resource="#xml/watch_face"/>
<meta-data
android:name="com.google.android.wearable.watchface.preview"
android:resource="#drawable/preview_complication_simple"/>
<meta-data
android:name="com.google.android.wearable.watchface.preview_circular"
android:resource="#drawable/preview_complication_simple"/>
<meta-data
android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
android:value="com.example.wearapp.CONFIG_COMPLICATION_SIMPLE"/>
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService"/>
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE"/>
</intent-filter>
</service>
<activity android:name="android.support.wearable.complications.ComplicationHelperActivity"/>
<activity
android:name=".ComplicationSimpleConfigActivity"
android:label="Fancy Watch">
<intent-filter>
<action android:name="com.example.wearapp.CONFIG_COMPLICATION_SIMPLE"/>
<category android:name="com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
I have a watch face service and a config activity. When I use ComplicationHelperActivity to createProviderChooserHelperIntent from the config activity I always get result cancelled in the onActivityResult. Here's how I start chooser activity and listen for the result
#Override
public void onClick(WearableListView.ViewHolder viewHolder) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onClick()");
}
Integer tag = (Integer) viewHolder.itemView.getTag();
ComplicationItem complicationItem = mAdapter.getItem(tag);
startActivityForResult(ComplicationHelperActivity.createProviderChooserHelperIntent(
getApplicationContext(),
complicationItem.watchFace,
complicationItem.complicationId,
complicationItem.supportedTypes),
PROVIDER_CHOOSER_REQUEST_CODE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PROVIDER_CHOOSER_REQUEST_CODE
&& resultCode == RESULT_OK) {
ComplicationProviderInfo complicationProviderInfo =
data.getParcelableExtra(ProviderChooserIntent.EXTRA_PROVIDER_INFO);
Log.d(TAG, "Selected Provider: " + complicationProviderInfo);
finish();
}
}
It seems like I'm missing complication support and that's why can't choose any provider. But to test this I copied ComplicationSimpleWatchFaceService from the WatchFace sample and still don't have any result. Here's complication code from the watch face.
private static final int LEFT_DIAL_COMPLICATION = 0;
private static final int RIGHT_DIAL_COMPLICATION = 1;
public static final int[] COMPLICATION_IDS = {LEFT_DIAL_COMPLICATION, RIGHT_DIAL_COMPLICATION};
public static final int[][] COMPLICATION_SUPPORTED_TYPES = {
{ComplicationData.TYPE_SHORT_TEXT},
{ComplicationData.TYPE_SHORT_TEXT}
};
private void initializeComplication() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "initializeComplications()");
}
mActiveComplicationDataSparseArray = new SparseArray<>(COMPLICATION_IDS.length);
mComplicationPaint = new Paint();
mComplicationPaint.setColor(Color.WHITE);
mComplicationPaint.setTextSize(COMPLICATION_TEXT_SIZE);
mComplicationPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
mComplicationPaint.setAntiAlias(true);
setActiveComplications(COMPLICATION_IDS);
}
Make sure that you have called the ComplicationHelperActivity.createProviderChooserHelperIntent method, to obtain an intent and to start the provider chooser.
Sample code (make sure to call the getActivity() method for it to launch):
startActivityForResult(
ComplicationHelperActivity.createProviderChooserHelperIntent(
getActivity(),
watchFace,
complicationId,
ComplicationData.TYPE_LARGE_IMAGE),
PROVIDER_CHOOSER_REQUEST_CODE);
The intent can be used with either startActivity or startActivityForResult to launch the chooser.
You can followed this tutorial and see if you missed some configurations.