requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent - push-notification

Fatal Exception: java.lang.IllegalArgumentException
: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
var pendingIntent: PendingIntent? = null
pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(this, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_MUTABLE)
} else {
PendingIntent.getActivity( this, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
}

Related

Are there diagnostic helpers for MS.DI configuration testing?

I'm setting up some testing to automatically check that our DI has been configured correctly. In particular we want to ensure that the lifestyles of dependancies match (so we don't get transients injected into singletons) and to avoid using the service locator as much as possible, relying on the constructor injection instead.
In the past we've used Castle.Windsor as our service provider, which comes with diagnostic classes and functions to help catch these problems. Are there similar functions for MS.DI or is it something we'll have to roll ourselves?
While I agree with the advice from Steven & Chris, developers are not always in charge of the infrastructure they have to use. Where possible I will be pushing for Castle.Windsor, since it's what my team is most familiar with, but in this case we managed to cobble together the tests we wanted ourselves.
I'm presenting the test pattern here in case someone stumbles across this question and has the same struggles, but please do consider using a better DI provider, especially if you're still at the start of your project.
[Test]
public void Assert_Lifetimes_Are_Consistent()
{
var missing = new List<string>();
var errors = new HashSet<Tuple<string, string>>();
foreach (var service in _serviceCollection.Where(s => IsInYourAssembly(s.ServiceType)))
{
var serviceLifetimeRanking = LifetimeRanking(service.Lifetime);
foreach (var fieldInfo in ((System.Reflection.TypeInfo)service.ServiceType).DeclaredFields.Where(fi => fi.FieldType.IsAbstract && IsInYourAssembly(fi.FieldType)))
{
var dependencyLifetime = _serviceCollection.SingleOrDefault(fi => fi.ServiceType == fieldInfo.FieldType)?.Lifetime;
if (dependencyLifetime==null)
missing.Add($"No service found for {fieldInfo.FieldType.FullName} as a dependency for {service.ServiceType.FullName}");
var dependencyLifetimeRanking = LifetimeRanking(dependencyLifetime);
if (dependencyLifetimeRanking > serviceLifetimeRanking)
errors.Add(
Tuple.Create(
$"{service.ServiceType.Name} ({service.Lifetime})",
$"{fieldInfo.FieldType.Name} ({dependencyLifetime})"
)
);
}
}
if (missing.Any()||errors.Any())
{
var sb = new StringBuilder();
sb.AppendJoin(Environment.NewLine, missing);
if (errors.Any())
{
sb.AppendLine("Following dependency pairs have inconsistent lifestyles:");
sb.AppendLine(string.Join(Environment.NewLine, errors.Select(err => $"{err.Item1} -> {err.Item2}")));
}
Assert.Fail(sb.ToString());
}
}
private bool IsInYourAssembly(Type type)
{
return (type.Assembly.FullName?.IndexOf("YOUR_PROJECT_ASSEMBLY_HERE") ?? -1) == 0;
}
private int LifetimeRanking(ServiceLifetime serviceLifetime)
{
switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
return 1;
case ServiceLifetime.Scoped:
return 2;
case ServiceLifetime.Transient:
return 3;
default:
throw new ArgumentOutOfRangeException("serviceLifetime", serviceLifetime,
$"Value is not a known member of the ServiceLifetime enum");
}
}
If the test fails, it will return a list of missing dependancies followed by a list of incompatible dependancy lifetimes.
The _serviceCollection field needs to be populated and Startup(config, env).ConfigureServices(_serviceCollection); needs to be called before running the test.
The IsInYourAssembly is an important function to filter out all the generic types which are also returned in the _serviceCollection.

Can't get FCM Remote Notifications to display when Xamarin.Forms Android app is closed, stopped or not running

I used these instructions to add Azure Notification Hub FCM-based Remote Notifications to my Xamarin.Forms Android app.
https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-xamarin-forms-get-started-push
I can receive Remote Notifications when the app is open or running in the background, but when I close the application or stop the application I no longer receive Remote Notifications.
My test device is running API level 22, so I'm using the following method to to build the notification on the device.
Android.Support.V4.App.NotificationCompat.Builder builder = new
Android.Support.V4.App.NotificationCompat.Builder(this)
For API level 26+, I'm using the follow method along with a channel to build the notification on the device.
var builder = new Android.App.Notification.Builder(this)
I'm thinking that I have to use a BroadcastReceiver to achieve this, but I really have no idea after reading so many comments on this subject. My app is compiled using API 27 and targets API 27.
Method 1
I'm trying to create a BroadcastReceiver that will launch MyService using an Explicit Intent when a Notification arrives. Unfortunately, this does not work on my API Level 22 test device.
//[BroadcastReceiver]
//[IntentFilter(new[] { Android.Content.Intent.ActionBootCompleted })]
[BroadcastReceiver(Enabled = true, Exported = true)]
[IntentFilter(new[] { "com.xamarin.example.TEST" })]
public class MyBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string message = intent.GetStringExtra("message");
string title = intent.GetStringExtra("title");
string id = intent.GetStringExtra("id");
//Explicit Intent to launch MyService
Intent intent2 = new Intent(Application.Context, typeof(MyService));
intent2.PutExtra("message", message);
intent2.PutExtra("title", title);
intent2.PutExtra("id", id);
Application.Context.StartService(intent2);
}
}
// Service is exported and given a name so other applications can use it
[Service(Exported = true, Name = "com.mycompany.myapp.MyService")]
// Intent filter only needed for Implicit Intents
//[IntentFilter(new string[] { "com.xamarin.example.TEST" })]
public class MyService : Service
{
public static string PRIMARY_NOTIF_CHANNEL = "default";
public override IBinder OnBind(Intent intent)
{
return null;
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
var pm = PowerManager.FromContext(this);
var wakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "Notification");
wakeLock.Acquire();
string message = intent.GetStringExtra("message");
string title = intent.GetStringExtra("title");
string id = intent.GetStringExtra("id");
var intent2 = new Intent(this, typeof(MainActivity));
intent2.PutExtra("id", id);
intent2.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
NotificationManager notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
Android.Support.V4.App.NotificationCompat.Builder builder = new Android.Support.V4.App.NotificationCompat.Builder(this)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))
.SetVibrate(new long[1])
//1 is Hi
.SetPriority(1)
.SetLargeIcon(BitmapFactory.DecodeResource(Resources, SalesFlash.Droid.Resource.Drawable.Icon_lg))
.SetSmallIcon(MyApp.Droid.Resource.Drawable.icon)
.SetChannelId(PRIMARY_NOTIF_CHANNEL);
notificationManager = NotificationManager.FromContext(this);
notificationManager.Notify(0, builder.Build());
wakeLock.Release();
//return StartCommandResult.Sticky;
return base.OnStartCommand(intent, flags, startId);
}
public override void OnDestroy()
{
base.OnDestroy();
}
}
According to this post, a BroadCastReceiver won't work for FCM Notifications. https://stackoverflow.com/a/44287962/5360237
This post seems to show a BroadcastReceiver accepting a Notification.
https://stackoverflow.com/a/45616014/5360237
Any help is much appreciated. Thanks in advance!
Here is the best post that I've read that explains the problem. It also provides a manual solution for the affected devices. https://medium.freecodecamp.org/why-your-push-notifications-never-see-the-light-of-day-3fa297520793 I was able to get Notifications working (when app is closed, stopped or not running) on my Alcatel One Touch Pop by navigating to Settings - Apps and then swiping over to the Restricted tab. From here, I was able to uncheck my app.
NOTE: Messages are delivered to my Android app using the data payload. Even though I was receiving Notifications in the foreground and background, I went ahead and added the following permission to my AndroidManifest.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
This permission is used for Auto Start. I also used Azure App Center to distribute a signed APK to my test device. Hope this helps someone else.
UPDATE: I removed the RECEIVE_BOOT_COMPLETE permission and did a Debug Build. I closed the app out of Debug (connected to Visual Studio) by swiping it off the screen. I then sent a Notification and it did not display. I then re-opened the app (not being debugged by Visual Studio) and closed it by swiping it off the screen, sent a Notification and it worked. So, it had nothing to with the RECEIVE_BOOT_COMPLETE permission and it doesn't have to be a Release version / signed APK.

How to deliver app with prefilled realm-database

I would like to deliver my app with already prefilled data in my realm database. Do I have to simply copy it to the documents directory or is there some other things to do?
Realm's documentation has a section on "Bundling a Realm with an App":
It’s common to seed an app with initial data, making it available to your users immediately on first launch. Here’s how to do this:
First, populate the realm. You should use the same data model as your final, shipping app to create a realm and populate it with the data you wish to bundle with your app. Since realm files are cross-platform, you can use an OS X app (see our JSONImport example) or your iOS app running in the simulator.
In the code where you’re generating this realm file, you should finish by making a compacted copy of the file (see -[RLMRealm writeCopyToPath:error:]). This will reduce the Realm’s file size, making your final app lighter to download for your users.
Drag the new compacted copy of your realm file to your final app’s Xcode Project Navigator.
Go to your app target’s build phases tab in Xcode and add the realm file to the “Copy Bundle Resources” build phase.
At this point, your bundled realm file will be accessible to your app. You can find its path by using [[NSBundle mainBundle] pathForResource:ofType:].
You can either create a read-only realm by calling [RLMRealm realmWithPath:readOnly:error:]. Or, if you’d like to create a writable realm file based on this initial data, you can copy the bundled file to your application’s Documents directory using [[NSFileManager defaultManager] copyItemAtPath:toPath:error:] and then construct your new realm by using [RLMRealm realmWithPath:].
You can refer to our migration sample app for an example of how to use a bundled realm file.
Pre filled Realm-database For Android
Put your realm database in res/raw folder
and execute following code in activity:
// Copying realm database
copyBundledRealmFile(this.getResources().openRawResource(R.raw.default0), "default0.realm");
RealmConfiguration config0 = new RealmConfiguration.Builder()
.name("default0.realm")
.build();
realm = Realm.getInstance(config0);
private String copyBundledRealmFile(InputStream inputStream, String outFileName) {
try {
File file = new File(this.getFilesDir(), outFileName);
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, bytesRead);
}
outputStream.close();
return file.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
A much easier way is just creating an ad-hoc function to be called just when there is no data on your realm model ("MyModel", in this example), at first app launch:
let realm = try! Realm()
lazy var data: Results<MyModel> = { self.realm.objects(MyModel.self) }()
func populateDefaultData() {
if yourdata.count == 0 {
try! realm.write() {
let defaultData = ["Data1", "Data2", "Data3"]
for data in defaultData {
let newData = MyModel()
newData.data = data
realm.add(newData)
}
}
data = realm.objects(MyModel.self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
populateDefaultData()
}

DataProtectionProvider in the Identity sample project

The official Identity 2 sample project has the code below in UserManager.Create()
public static UserManager Create(IdentityFactoryOptions<UserManager> options, IOwinContext context) {
//...etc...
// --- what does this block do? ---
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null) {
manager.UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("ASP.NET Identity"));
}
// --------------------------------
//...etc...
}
The alpha/beta/RTM Identity documentation is bad or non-existent.
What does this do?
The protection provider in the following line is used as a token provider/generator.
manager.UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("ASP.NET Identity"));
It is responsible for generating an email confirmation token or a password reset token. If you do not set this line you won't be able to use this features (an appropriate exception will be thrown). An example can be found here.
Its main purpose is to provide an implementation of the IDataProtector interface (through the Create method) which encrypts and decrypts data. An implementation for this interface in the framework is the DpapiDataProtectionProvider which should be used when the application is not hosted by ASP.NET. There are several other implementations on the web (for example one which uses the machine key for security purposes). class For more information about the DataProtectorTokenProvider have a look at the MSDN documentation.
UPDATE
Extensive Data Protection documentation is now available.

Changing the Endpoint.Binding of a WCF System.ServiceModel.ClientBase doesn't work

I'm working with a programmatically configurated WCF Client (System.ServiceModel.ClientBase). This WCF Client is configured using a CustomBinding, which has a TextMessageEncodingBindingElement by default.
Now when I try to switch to Mtom encoding, I change the Client's Endpoint.Binding property, which works fine. The Endpoint.Binding property show's it has changed.
Unfortunately when I execute one of the methods the WCF service exposes, it still uses TextMessageEncoding and I can't figure out why.
I've got it working though, by constructing a new ClientBase and passing the new EndPointBinding in the constructor:
socialProxy = new SocialProxyClient(SocialProxyClientSettings.SocialProxyMTomEndPointBinding, new EndpointAddress(SocialProxyClientSettings.SocialProxyEndPointAddress));
But when I try this it doesn't work:
socialProxy.Endpoint.Binding = SocialProxyClientSettings.SocialProxyMTomEndPointBinding;
These are my definitions for the EndPointBindings:
public static TextMessageEncodingBindingElement TextMessageEncodingBindingElement
{
get
{
if (_textMessageEncodingBindingElement == null)
{
_textMessageEncodingBindingElement = new TextMessageEncodingBindingElement() { MessageVersion = MessageVersion.Soap11 };
_textMessageEncodingBindingElement.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
{
MaxDepth = 32,
MaxStringContentLength = 5242880,
MaxArrayLength = 204800000,
MaxBytesPerRead = 5242880,
MaxNameTableCharCount = 5242880
};
}
return _textMessageEncodingBindingElement;
}
}
public static MtomMessageEncodingBindingElement MtomMessageEncodingBindingElement
{
get
{
if (_mtomMessageEncodingBindingElement == null)
{
_mtomMessageEncodingBindingElement = new MtomMessageEncodingBindingElement();
_mtomMessageEncodingBindingElement.MaxReadPoolSize = TextMessageEncodingBindingElement.MaxReadPoolSize;
_mtomMessageEncodingBindingElement.MaxWritePoolSize = TextMessageEncodingBindingElement.MaxWritePoolSize;
_mtomMessageEncodingBindingElement.MessageVersion = TextMessageEncodingBindingElement.MessageVersion;
_mtomMessageEncodingBindingElement.ReaderQuotas.MaxDepth = TextMessageEncodingBindingElement.ReaderQuotas.MaxDepth;
_mtomMessageEncodingBindingElement.ReaderQuotas.MaxStringContentLength = TextMessageEncodingBindingElement.ReaderQuotas.MaxStringContentLength;
_mtomMessageEncodingBindingElement.ReaderQuotas.MaxArrayLength = TextMessageEncodingBindingElement.ReaderQuotas.MaxArrayLength;
_mtomMessageEncodingBindingElement.ReaderQuotas.MaxBytesPerRead = TextMessageEncodingBindingElement.ReaderQuotas.MaxBytesPerRead;
_mtomMessageEncodingBindingElement.ReaderQuotas.MaxNameTableCharCount = TextMessageEncodingBindingElement.ReaderQuotas.MaxNameTableCharCount;
}
return _mtomMessageEncodingBindingElement;
}
}
Can someone explain why changing the Endpoint.Binding programmatically doesn't work?
I believe that during construction of the ClientBase, the original Binding is used to create some helper objects. Changing the binding later does not change those helper objects.
To make any adjustments after construction, you likely need a custom Binding Behavior that you can tweak the internals of the Binding as you need. Use that in the construction so all helper objects are prepared for your later changes. As usual, all you want is one simple behavior change, but you will need to also write the ancillary helper classes to support your one behavior change.
See the SO thread: ONVIF Authentication in .NET 4.0 with Visual Studio 2010
For a discussion of CustomBinding issues.
See the blog post: Supporting the WS-I Basic Profile Password Digest in a WCF Client Proxy
For an example of a custom Behavior that lets you change the Username Token on the fly.
Perhaps something similar can be done to let you control the local endpoint binding on the fly.
UPDATE: More reading here in StackOverflow, and pages it links to and I believe i have found the answer you are looking for.
For PasswordDigestBehavior:
see: ONVIF Authentication in .NET 4.0 with Visual Studios 2010
and: http://benpowell.org/supporting-the-ws-i-basic-profile-password-digest-in-a-wcf-client-proxy/
For local NIC binding:
see: Specify the outgoing IP address to use with WCF client
// ASSUMPTIONS:
// 1: DeviceClient is generated by svcutil from your WSDL.
// 1.1: DeviceClient is derived from
// System.ServiceModel.ClientBase<Your.Wsdl.Device>
// 2: serviceAddress is the Uri provided for your service.
//
private static DeviceClient CreateDeviceClient(IPAddress nicAddress,
Uri serviceAddress,
String username,
String password)
{
if (null == serviceAddress)
throw new ArgumentNullException("serviceAddress");
//////////////////////////////////////////////////////////////////////////////
// I didn't know how to put a variable set of credentials into a static
// app.config file.
// But I found this article that talks about how to set up the right kind
// of binding on the fly.
// I also found the implementation of PasswordDigestBehavior to get it all to work.
//
// from: https://stackoverflow.com/questions/5638247/onvif-authentication-in-net-4-0-with-visual-studios-2010
// see: http://benpowell.org/supporting-the-ws-i-basic-profile-password-digest-in-a-wcf-client-proxy/
//
EndpointAddress serviceEndpointAddress = new EndpointAddress(serviceAddress);
HttpTransportBindingElement httpBinding = new HttpTransportBindingElement();
if (!String.IsNullOrEmpty(username))
{
httpBinding.AuthenticationScheme = AuthenticationSchemes.Digest;
}
else
{
httpBinding.AuthenticationScheme = AuthenticationSchemes.Anonymous;
}
var messageElement = new TextMessageEncodingBindingElement();
messageElement.MessageVersion =
MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None);
CustomBinding bind = new CustomBinding(messageElement, httpBinding);
////////////////////////////////////////////////////////////////////////////////
// from: https://stackoverflow.com/questions/3249846/specify-the-outgoing-ip-address-to-use-with-wcf-client
// Adjust the serviceEndpointAddress to bind to the local NIC, if at all possible.
//
ServicePoint sPoint = ServicePointManager.FindServicePoint(serviceAddress);
sPoint.BindIPEndPointDelegate = delegate(
System.Net.ServicePoint servicePoint,
System.Net.IPEndPoint remoteEndPoint,
int retryCount)
{
// if we know our NIC local address, use it
//
if ((null != nicAddress)
&& (nicAddress.AddressFamily == remoteEndPoint.AddressFamily))
{
return new System.Net.IPEndPoint(nicAddress, 0);
}
else if (System.Net.Sockets.AddressFamily.InterNetworkV6 == remoteEndPoint.AddressFamily)
{
return new System.Net.IPEndPoint(System.Net.IPAddress.IPv6Any, 0);
}
else // if (System.Net.Sockets.AddressFamily.InterNetwork == remoteEndPoint.AddressFamily)
{
return new System.Net.IPEndPoint(System.Net.IPAddress.Any, 0);
}
};
/////////////////////////////////////////////////////////////////////////////
DeviceClient client = new DeviceClient(bind, serviceEndpointAddress);
// Add our custom behavior
// - this requires the Microsoft WSE 3.0 SDK file: Microsoft.Web.Services3.dll
//
PasswordDigestBehavior behavior = new PasswordDigestBehavior(username, password);
client.Endpoint.Behaviors.Add(behavior);
return client;
}

Resources