Why is READ_CONTACTS permission not being read in .NET MAUI? - maui

I've been reading on how to access the address book/contacts. I'm on a PC and have an android emulator up and running logged into my gmail. I run the code below. Up pops my address book contacts, so that is good. Unfortunately, when I select a contact, the next line isn't hit. Below the source is some output that I see in the Output window. I see some nulls in the output, but I don't know what to make of it.
the behavior i get is that after I select a contact, that window closes, and the previous screen is displayed. When I trap the exception, I get the message that the app doesn't have the READ_CONTACTS permission, yet, I am obviously setting it. I've followed the instructions here on setting things up: https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/communication/contacts?tabs=android
It seems as if the permission isn't being read in the manifest or out of the assemblyinfo.cs file. Am I missing something? Does it wound like I've done something wrong?
TIA,
Wally
My Manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="#mipmap/appicon" android:roundIcon="#mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
</manifest>
Source:
var contact = await Contacts.Default.PickContactAsync(); // <-- break point is hit here
try{
if (contact == null) // <-- Breakpoint isn't hit
return;
string id = contact.Id;
string namePrefix = contact.NamePrefix;
string givenName = contact.GivenName;
string middleName = contact.MiddleName;
string familyName = contact.FamilyName;
string nameSuffix = contact.NameSuffix;
string displayName = contact.DisplayName;
List<ContactPhone> phones = contact.Phones; // List of phone numbers
List<ContactEmail> emails = contact.Emails; // List of email addresses
}
catch (Exception ex)
{
// Most likely permission denied
}

Many thanks to #toolmakersteve. I added this code and called it. It still need refinement, but it's a start.
public async Task<PermissionStatus> CheckAndRequestContactsReadPermission()
{
PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.ContactsRead>();
if (status == PermissionStatus.Granted)
return status;
if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
{
// Prompt the user to turn on in settings
// On iOS once a permission has been denied it may not be requested again from the application
return status;
}
if (Permissions.ShouldShowRationale<Permissions.ContactsRead>())
{
// Prompt the user with additional information as to why the permission is needed
}
status = await Permissions.RequestAsync<Permissions.ContactsRead>();
return status;
}

Related

How to save/share a file in Net. Maui without savefiledialog?

My MauiBlazorApp can create a .pdf file. In what ways can I make this file accessible for the users of windows/android/IOS. A SaveFile Dialog is not yet implemented in Maui. With this path
string SaveToPath = System.IO.Path.Combine(FileSystem.Current.AppDataDirectory, "hello.pdf");
I can save it on a windows machine and open it (tested).
The path (FileSystem.Current.AppDataDirectory) on a local android device (phone connected to windows maschine via usb) is something like this:
/data/user/0/com.companyname.appname/files/hello.pdf
But later I can't find this folder on my phone. I find this:
/data/com.companyname.appname/cache/ empty folders all the way
Why can't I save a file in .../downloads
How do you import/export data from 'device to device' in Maui? Files seems to be not the way. Email with attechments? Is that possible/better?
You can check the Maui File picker, It provides the method you can pick the file from the device.
public async Task<FileResult> PickAndShow(PickOptions options)
{
try
{
var result = await FilePicker.Default.PickAsync(options);
if (result != null)
{
if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase))
{
using var stream = await result.OpenReadAsync();
var image = ImageSource.FromStream(() => stream);
}
}
return result;
}
catch (Exception ex)
{
// The user canceled or something went wrong
}
return null;
}
In addition, you can refer to Folder Picker .NET MAUI. This is more detailed.

GetTokenTask receive a empty token, please check HmsMessageService.onNewToken receive result

My word game published at Huawei AppGallery uses Account and Push Kits:
implementation 'com.huawei.hms:hwid:6.4.0.300'
implementation 'com.huawei.hms:push:6.3.0.302'
The Account Kit works well and I am able to obtain the open id and display name of the phone user.
This means, that the SHA-256 certificate fingerprints are configured properly and are not causing the problem with Push Kit described below.
The problem: the push token is not obtainable on an EMUI 9.1 phone (see screenshot at the very bottom).
AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application>
<service
android:name="de.afarber.HmsService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
HmsService.java:
public class HmsService extends HmsMessageService {
#Override
public void onNewToken(String token) { // THIS IS NEVER CALLED !!!
super.onNewToken(token);
Log.d(Utils.TAG,"onNewToken token=" + token);
}
}
My custom Application class:
private final ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
#Override
public void onCreate(#NonNull Context context) {
HmsMessaging.getInstance(this).setAutoInitEnabled(true);
mExecutor.execute(() -> {
try {
String appId = context.getString(R.string.huawei_app_id); // VALUE IN DEBUGGER: "102776361"
String token = HmsInstanceId.getInstance(context).getToken(appId, "HCM"); // ALWAYS EMPTY
if (!TextUtils.isEmpty(token)) {
// this only supposed to work for EMUI 10 or newer
Log.d(Utils.TAG,"getToken token=" + token);
}
} catch (Exception ex) {
Log.w(TAG,"getToken failed", ex);
}
});
}
When I debug the app at my Huawei ANE-LX1 phone I see in the logcat:
I/HMSSDK_HMSPackageManager: <initHmsPackageInfoForMultiService> Succeed to find HMS apk: com.huawei.hwid version: 60400311
...
I/HMSSDK_PendingResultImpl: init uri:push.gettoken
...
I/HMSSDK_HmsClient: post msg api_name:push.gettoken, app_id:102776361|, pkg_name:com.wordsbyfarber.huawei, sdk_version:60400300, session_id:*, transaction_id:102776361ttoken20220330184241694787985, kitSdkVersion:60300301, apiLevel:1
I/HMSSDK_BaseAdapter: In constructor, activityWeakReference is java.lang.ref.WeakReference#343238b, activity is de.afarber.MainActivity#50109d0
I/HMSSDK_BaseAdapter: in baseRequest + uri is :push.gettoken, transactionId is : 102776361ttoken20220330184241694787985
I/HMSSDK_PendingResultImpl: init uri:push.gettoken
I/HMSSDK_PendingResultImpl: setResultCallback
I/HMSSDK_PendingResultImpl: setResult:0
...
I/HMSSDK_BaseAdapter: baseCallBack.onComplete
I/HMSSDK_HmsClient: receive msg status_code:0, error_code:0, api_name:push.gettoken, app_id:102776361|, pkg_name:com.wordsbyfarber.huawei, session_id:*, transaction_id:102776361ttoken20220330184241642989857, resolution:null
I/HMSSDK_TaskApiCall: doExecute, uri:push.gettoken, errorCode:0, transactionId:102776361ttoken20220330184241642989857
I/HMSSDK_HmsInstanceId: GetTokenTask receive a empty token, please check HmsMessageService.onNewToken receive result.
I/HMSSDK_HMSPackageManager: Enter getHMSPackageNameForMultiService
I/HMSSDK_RequestManager: removeReqByTransId
I/HMSSDK_BaseAdapter: api is: push.gettoken, resolution: null, status_code: 0
I/HMSSDK_BaseAdapter: baseCallBack.onComplete
I/HMSSDK_HmsClient: receive msg status_code:0, error_code:0, api_name:push.gettoken, app_id:102776361|, pkg_name:com.wordsbyfarber.huawei, session_id:*, transaction_id:102776361ttoken20220330184241694787985, resolution:null
I/HMSSDK_TaskApiCall: doExecute, uri:push.gettoken, errorCode:0, transactionId:102776361ttoken20220330184241694787985
I/HMSSDK_HmsInstanceId: GetTokenTask receive a empty token, please check HmsMessageService.onNewToken receive result.
I/HMSSDK_HMSPackageManager: Enter getHMSPackageNameForMultiService
I/HMSSDK_RequestManager: removeReqByTransId
I/HMSSDK_AutoInit: Push init succeed
And by setting debugger breakpoints and inspecting logcat for my logs I see that:
I call getToken() and pass it my app id "102776361"
The getToken() takes some time and then returns an empty token (which is expected for EMUI 9.x)
However the HmsService method onNewToken() is never called (which is NOT OK)
I have unsuccessfully tried multiple things to resolve my issue -
Tried adding following meta data to the AndroidManifest.xml:
Tried obtaining AAID manually in onCreate (obtaining AAID works OK, but token is still not delivered):
HmsInstanceId.getInstance(context).getAAID()
.addOnSuccessListener(aaidResult -> Log.d(TAG, "getAAID aaid=" + aaidResult.getId()))
.addOnFailureListener(ex -> Log.w(TAG, "getAAID failed", ex));
Tried adding more permissions to the AndroidManifest.xml:
android.permission.READ_PHONE_STATE
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_WIFI_STATE
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.REQUEST_INSTALL_PACKAGES
Tried exporting HmsService in the AndroidManifest.xml (this is not recommended according to #shirley!):
<service
android:name="de.afarber.HmsService"
android:enabled="true"
android:exported="true"
android:permission="${applicationId}.permission.PROCESS_PUSH_MSG"
android:process=":HmsMessageService">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
Tried overriding public void onNewToken(String token, Bundle bundle) in HmsService.java
My EMUI 9.1 phone is up to date and I am located in Germany:
UPDATE:
I have prepared a simple test case at Github and there the push token is delivered to the onNewToken() method just fine:
After adding onCreate() to the both custom HmsMessageService classes I have noticed that the method is called in the test app, but not in my real app.
Unfortunately, I have not found the reason for that yet... Here a screenshot of the merged AndroidManifest.xml:
I have tried starting the service by running the following line in the onCreate() method of my custom Application class:
startService(new Intent(this, de.afarber.HmsService.class));
And the line does not fail and I can see the onCreate() method of the HmsService being run. However the onNewToken() method is still not called. I wonder if some additional registration is needed in the HMS Core Android code after starting the service "manually".
UPDATE 2:
After rechecking all settings and I have noticed, that I didn't enable HUAWEI Push Kit in the AppGallery Connect:
After enabling that setting I am able to receive push token on my Huawei Phone.
And after auto-updating I am even receiving a push token on a non-Huawei phone: Moto G Play.
We are checking on your issue. Can you help to confirm:
Do you use 3rd Push platform?
How many classes you have which extends HmsMessageService?
Don't config the following items:
android:permission="${applicationId}.permission.PROCESS_PUSH_MSG"
android:process=":HmsMessageService"
After the confirmation above, you can share more log to me.
You can collect the log with this command: "adb logcat -v time > D:\hwpush.log".
In addition, you can search the file "push.log" in file manager, and share it here: wwwjamescom#sina.com .
Here a quick check list
Make sure the push kit is enable for your app
Make sure the correct agconnect-services.json is copy to the app directory. If you have more than one copy
You can use the Push kit console to test sending notification to your app to verify that your setting is corrected
You can use the automatic initialization to test if you can receive the push token.
Here the link to document https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-client-dev-0000001050042041#section13546121751811
Here is the snippet of my code that received the token just fine.
in the MainActivity
public class MainActivity extends AppCompatActivity implements OSSubscriptionObserver {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setAutoInitEnabled(true);
}
private void setAutoInitEnabled(final boolean isEnable) {
if(isEnable){
// Enable automatic initialization.
HmsMessaging.getInstance(this).setAutoInitEnabled(true);
} else {
// Disable automatic initialization.
HmsMessaging.getInstance(this).setAutoInitEnabled(false);
}
}
}
in the class that response to the HMS messaging service
public class MyHMSService extends HmsMessageService {
/\*\*
\* When an app calls the getToken method to apply for a token from the server,
\* if the server does not return the token during current method calling,
\* the server can return the token through this method later.
\* This method callback must be completed in 10 seconds.
\* Otherwise, you need to start a new Job for callback processing.
\*
\* #param token token
\* #param bundle bundle
\*/
#Override
public void onNewToken(String token, Bundle bundle) {
Toast.makeText(getApplicationContext(), "token value is: " + token, Toast.LENGTH_SHORT).show();
}
In case you still have the problem of receiving the message, please try a sample code and send a message from
AGC.developer.huawei.com/consumer/en/doc/development/…

Getting UserId of current user Blazor webassembly

So I am writing a Blazor webassembly application, with asp.ner core Identity. I need to get the ID of the current user, not the username that the methods in Identy give.
The method
Context. User.identity.name
gives the username but I need the ID for a fk in a model/table.
I can't use the username as usernames might change.
I have searched the net, however I keep seeing just the username returned.
Any assistance will be greatly appreciated.
I use this with the boiler plate Identity Server:
#page "/claims"
#inject AuthenticationStateProvider AuthenticationStateProvider
<h3>ClaimsPrincipal Data</h3>
<p>#_authMessage</p>
#if (_claims.Count() > 0)
{
<table class="table">
#foreach (var claim in _claims)
{
<tr>
<td>#claim.Type</td>
<td>#claim.Value</td>
</tr>
}
</table>
}
<p>#_userId</p>
#code {
private string _authMessage;
private string _userId;
private IEnumerable<Claim> _claims = Enumerable.Empty<Claim>();
protected override async Task OnParametersSetAsync()
{
await GetClaimsPrincipalData();
await base.OnParametersSetAsync();
}
private async Task GetClaimsPrincipalData()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
_authMessage = $"{user.Identity.Name} is authenticated.";
_claims = user.Claims;
_userId = $"User Id: {user.FindFirst(c => c.Type == "sub")?.Value}";
}
else
{
_authMessage = "The user is NOT authenticated.";
}
}
}
In Startup.cs, add the following line in ConfigureServices
services.AddHttpContextAccessor();
In your Blazor component, add the following lines on the top of the file
#using System.Security.Claims
#inject IHttpContextAccessor HttpContextAccessor
In your method, add the following lines to get the UserId
var principal = HttpContextAccessor.HttpContext.User;
var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
Not an answer, just a tip on using breakpoints to find the answer. My site is Blazor Server, so it's very possible that things are different-- in my case, Brian Parker's solution didn't work for me, so I did the following:
var user = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User;
if (true) {} // or any other code here, breakpoint this line
If you set a breakpoint right after retrieving the user, run the app and hover the user variable in the code when it breaks, it will pop up the complete object. By hovering various fields, you can investigate. I found that the claim type strings were big long things like "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
So the answer that worked for me was:
var user = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User;
string userId = user.FindFirst(c => c.Type.Contains("nameidentifier"))?.Value;
My point is that when the docs are complicated, or when the technology is changing fast so that one day's right answer is the next day's wrong lead, you can achieve a lot just by using VS to dig around.
Hope that helps someone. :D

Check Instagram Username for existence

I am trying to validate an Instagram username, that the user should enter. I want to find out whether an account with the username exists or if not. I have tried it this way:
final request = await http.get('https://www.instagram.com/$username/');
if (request.statusCode != 404) {
...
}
I also looked at a similar question: Check if instagram account name is available
The link that is mentioned in the answer:(https://www.instagram.com/username/?__a=1) works fine in the browser
(If the username exists, it shows all the information of the account, if it doesn't it shows two brackets that are empty)
but in my code I don't get statusCode 404 I get statusCode 200.
I also can't catch an error.
It is the same with the link from my code (https://www.instagram.com/$username/), I get statusCode 200.
Is there a different way that I have not yet considered?
Thanks for your answers in advance!
It's just a http request, if you are getting a success status so check the body in the request, for example if you get a success code but the body is empty {} you can decode the body response and check it in this way:
void checkUser() async {
final request = await http.get('https://www.instagram.com/fakeUserExample/?__a=1');
final decodeData = json.decode(request.body);
if(decodeData.isEmpty){
print(".:: User Not Found ::.");
}
}
Don't forget to import the import 'dart:convert';
Hope it helps.

Windows Phone 10 Univrsal - get online contacts

I can get all the contacts from a windows phone 10 device.
I want to know if a contact is local contact or an online contact.
I used this code to indicate:
ContactStore contactstore = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AllContactsReadOnly);
ContactList = await contactstore.FindContactsAsync();
if (ContactList.Count > 0)
{
ienum = ContactList.GetEnumerator();
ienum.MoveNext();
IEnumerator<ContactConnectedServiceAccount> enum2 = ienum.Current.ConnectedServiceAccounts.GetEnumerator();
enum2.MoveNext();
string id = enum2.Current.ServiceName;
but it get to the catch exception scope..
I also tried to change the access type from AllContactsReadOnly to AppContactsReadWrite and it doesn't work and does the same.