Blazor MAUI - Camera and Microphone Android permissions - android

I am trying to show live stream from camera & microphone in <video> html element.
In blazor file (sample.razor) I invoke method to request and show video:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("requestMediaAndShow");
}
}
In javascript file (sample.js) I request stream and assign to video html element.
// Create request options
let options = {
audio: true,
video: true
};
// Request user media
navigator.mediaDevices
.getUserMedia(options)
.then(gotLocalStream)
.catch(logError);
But when I am requesting i catch an error like "NotAllowedError: Permission denied".
AndroidManifest.xml contains:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
MainActivity.cs contains:
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.Camera, Manifest.Permission.RecordAudio, Manifest.Permission.ModifyAudioSettings }, 0);
}
}
Any ideas how to request audio and video stream by javascript in BlazorWebView natively on android?
PS. On both on website and natively on Windows platform works great and no extra permissions are required.

Although the Android permissions seem to be granted OK, I suspected the website permissions were not. Not sure if the author of this question is the same person, but an issue was opened on the .NET MAUI repo as well about this.
While we are looking into making this work out of the box, another helpful user has posted this workaround for now.
Implement your own handler like so
public class MauiWebChromeClient : WebChromeClient
{
public override void OnPermissionRequest(PermissionRequest request)
{
request.Grant(request.GetResources());
}
}
public class MauiBlazorWebViewHandler : BlazorWebViewHandler
{
protected override WebChromeClient GetWebChromeClient()
{
return new MauiWebChromeClient();
}
}
And register this handler in your MauiProgram.cs:
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<IBlazorWebView, MauiBlazorWebViewHandler>();
});
This will automatically grant all the requested permissions from the web side of things.

I think you are missing some parts of the permission checks. You should read this section about runtime permissions that were introduced in Android 6.0. For example, I cannot see any override of OnRequestPermissionsResult in MainActivity.
https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/permissions?tabs=macos#runtime-permission-checks-in-android-60

Related

Xamarin Forms - How to check setting of RequestIgnoreBatteryOptimizations permission on Android

(See UPDATE below)
I have a Xamarin Forms app on Android which uses the Xamarin.Essentials library.
The app requires to run in the background to be fed location data (not particularly relevant to the question in hand, but included for context), and so must not be put to sleep by any battery optimisations that the OS might attempt.
I know that the user can manually opt out specific apps from Battery Optimizations, but as it is so crucial to the successful operation of the app, I would like the app to be able to :
check the Battery Optimization Opt-out permission status to ensure it is appropriately set,
and/or
force Android to opt the app of any battery optimizations.
I have added an entry into AndroidManifest.xml, but it doesn't seem to help, with the newly-installed app defaulting to being Battery Optimized.
AndroidManifest.xml
The manifest contains the following entry:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Xamarin.Essentials
This library gives access to a number of permission-related info on the device, but Battery Optimizations doesn't seem to be one of them.
Device being used
I don't know if it's relevant, but I am testing on a Samsung Galaxy S20 Ultra.
Can anyone offer any advice?
UPDATE Aug 28 2021
Following the advice from contributors and with reference to the docs at https://learn.microsoft.com/en-us/xamarin/essentials/permissions?tabs=android#extending-permissions ...
In My Shared Code
public interface IRequestIgnoreBatteryOptimizationPermission
{
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
In My Android Project
[assembly: Xamarin.Forms.Dependency(typeof(RequestIgnoreBatteryOptimizationPermission))]
namespace MyAndroidProject
{
public class RequestIgnoreBatteryOptimizationPermission : Permissions.BasePlatformPermission, IRequestIgnoreBatteryOptimizationPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string androidPermission, bool isRuntime)>
{
(Android.Manifest.Permission.RequestIgnoreBatteryOptimizations, true)
}.ToArray();
}
}
On Shared App Initialization
// Ensure Required Permissions have been granted
var requestIgnoreBatteryOptimizationPermission = DependencyService.Get<IRequestIgnoreBatteryOptimizationPermission>();
var status = requestIgnoreBatteryOptimizationPermission.CheckStatusAsync().Result;
if (status != PermissionStatus.Granted)
{
status = requestIgnoreBatteryOptimizationPermission.RequestAsync().Result;
}
Result...
On calling CheckStatusAsync, the result comes back as Granted.
But the app settings still say otherwise...
I've tried it on both a physical device (Samsung Galaxy S20 Ultra) and on an Android Emulator (Pixel 2 API 28), with same result on both.
From this document, there are two ways to set Battery Optimization
An app can fire the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent to take the user directly to the Battery Optimization, where they can add the app.
An app holding the REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission can trigger a system dialog to let the user add the app to the exemption list directly, without going to settings. The app fires a ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS Intent to trigger the dialog.
I use PowserManager.isIgnoringBatteryOptimizations to check Battery Optimization.
Firstly, add RequestIgnoreBatteryOptimizations in AndroidManifest.xml
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Then creating Interface in Shared code.
public interface IBattery
{
void getbattery();
}
Implementing this interface in Android platform.
[assembly: Dependency(typeof(ImplementBattery))]
namespace FormsSample.Droid
{
public class ImplementBattery : IBattery
{
public void getbattery()
{
Intent intent = new Intent();
String packageName = MainActivity.mac.PackageName;
PowerManager pm = (PowerManager)MainActivity.mac.GetSystemService(Context.PowerService);
if (pm.IsIgnoringBatteryOptimizations(packageName))
intent.SetAction(Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings);
else
{
intent.SetAction(Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetData(Android.Net.Uri.Parse("package:" + packageName));
}
MainActivity.mac.StartActivity(intent);
}
}
}
Creating static Mainactivity field in Mainactivity.cs.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public static MainActivity mac;
protected override void OnCreate(Bundle savedInstanceState)
{
initFontScale();
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
mac = this;
Now, using DependencyService to fire.
private void Button_Clicked(object sender, EventArgs e)
{
DependencyService.Get<IBattery>().getbattery();
}
In your MainActivity add two properties:
private const int RequestPermissionsId = 0
and
private readonly string[] ManifestPermissions =
{
Manifest.Permission.RequestIgnoreBatteryOptimizations
// Add here other permissions you want to check
}
Then override the OnStart method and force permission check there:
public override void OnStart()
{
base.OnStart();
if ((int)Build.VERSION.SdkInt >= 23)
{
if (CheckSelfPermission(Manifest.Permission.RequestIgnoreBatteryOptimizations != Permission.Granted)
RequestPermissions(ManifestPermissions, RequestPermissionsId);
}
}
And of course, remember that you must have the OnRequestPermissionsResult method implemented:
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
Well, I am guessing you are trying to check it directly through shared code
The easiest way to do it would be to extend the needed permission in native using essentials.
Check the status and request permission through an interface:
public interface IRequestIgnoreBatteryOptimizations
{
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
Implement the native part for the same:
public class IgnoreBatteryOptimizationsPlatformPermission : Xamarin.Essentials.Permissions.BasePlatformPermission, IRequestIgnoreBatteryOptimizations
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new (string, bool)[] { (Manifest.Permission.RequestIgnoreBatteryOptimizations, true) };
}
Then register it in your Native XF caller class(MainActivity, Appdelegate(for some other permission))
DependencyService.Register<IRequestIgnoreBatteryOptimizations, IgnoreBatteryOptimizationsPlatformPermission>();
And then in your XF class use this method:
public static async Task<PermissionStatus> CheckAndRequestBatteryOptimizations()
{
var batteryOptimizationsPermission = DependencyService.Get<IRequestIgnoreBatteryOptimizations>();
var status = await batteryOptimizationsPermission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await batteryOptimizationsPermission.RequestAsync();
}
return status;
}
And then request for it whenever you like:
var status= await CheckAndRequestBatteryOptimizations();
More information here: https://learn.microsoft.com/en-us/xamarin/essentials/permissions?tabs=android#extending-permissions

Managing AndroidPermissionRequest and MediaPermissionRequest using Geckoview Android

I am currently having a problem on managing requests using geckoview. Android built in webview is not an option for me because the website I want to open is not compatible with chrome. It can be opened only using mozilla so geckoview is my alternative.
The problem I have is granting permission on using the microphone and recording audio. Because the website I am trying to open in geckoview records audio (voice Collection).
I'am new to android and geckoview thats why the guide I use is this project https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
I was able to show the request permission and accept it but it seems my application doesn't store the permission result. Currently I am trying my program to this website https://www.onlinemictest.com
This is my PermissionDelegate
private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate {
public int androidPermissionRequestCode = 1;
#Override
public void onAndroidPermissionsRequest(GeckoSession session, String[] permissions, Callback callback)
{
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
Log.i(TAG, "Android Permission Needed");
requestPermissions(permissions, androidPermissionRequestCode);
callback = new ExamplePermissionCallback();
callback.grant();
}
else{
Log.i(TAG, "Android Permission Granted");
callback.grant();
}
}
#Override
public void onContentPermissionRequest (GeckoSession session, String uri, int type, String access, Callback callback)
{
Log.i(TAG, "Content Permission Needed");
}
#Override
public void onMediaPermissionRequest (GeckoSession session, String uri, MediaSource[] video, MediaSource[] audio, MediaCallback callback)
{
Log.i(TAG, "Media Permission Needed");
}
}
and this is my PermissionDelegateCallback
public class ExamplePermissionCallback implements GeckoSession.PermissionDelegate.Callback{
#Override
public void grant() {
int permission = ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.RECORD_AUDIO);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.RECORD_AUDIO},
RECORD_REQUEST_CODE);}
}
#Override
public void reject() {
}
}
The result says 'Android Permission Granted' and after that, it shows the Log I put which is 'Media Permission Needed' and the website says 'Waiting for microphone'
I also checked the application on my phone and it already has the microphone permission.
GeckoView has two levels of permissions:
The Android-level permission which Android grants to your app and you seem to be requesting correctly
The content-level permission which GeckoView grants to the specific web page and is not granted in your example.
In short, just because your app has permission to listen to the microphone, that doesn't mean that all web pages that you open in GeckoView will have access to the microphone.
When a page requests a media permission you get a onMediaPermission callback which you would need to accept using callback.grant, an example of this is here: https://searchfox.org/mozilla-central/rev/3483fb259b4edbe4594cfcc3911db97d5441b67d/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java#927
The audio argument of onMediaPermission contains the list of all the audio sources (most likely you'll only have one, the microphone) which you can use to accept the prompt for the right audio source calling
#Override
public void onMediaPermissionRequest(
GeckoSession session,
String uri,
MediaSource[] video,
MediaSource[] audio,
MediaCallback callback)
{
// Find out which audio source is the microphone
final int MICROPHONE_INDEX = ...;
// Grant the request
callback.grant(null, audio[MICROPHONE_INDEX]);
}
Note if you also need video, you can do the same thing with the video argument and the appropriate video source.
To figure out which one is the microphone you can use this snippet as example (look for SOURCE_MICROPHONE) https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java#1201-1223

How can I get the permissions of my Android / iOS application from my WebView in javascript?

I would like to know how I could obtain or read the permissions that the user granted in my application from a webview in javascript.
For example, in my webview I occupy the camera, but if the user denied access to the camera, as I could know from my webview in javascript that the use of the camera is denied and not show the option to the user (because that would truncate my app
PD: Preferably not involving modifications in IOS / ANDROID.
Thanks..
What you need is a JavaScript Interface:
webView.addJavascriptInterface(new JavaScriptInterface(this), "myApplication");
JavaScriptInterface.java:
public class JavaScriptInterface {
private WeakReference<Activity> mActivityWeakReference;
public JavaScriptInterface(Activity activity) {
this.mActivityWeakReference = new WeakReference<>(activity);
}
#JavascriptInterface
public void requestCameraPermission() {
if (mActivityWeakReference.get() != null) {
ActivityCompat.requestPermission(mActivityWeakReference.get(), ....);
}
}
}
And then in your JavaScript code:
window.myApplication = function AndroidClass(){};
window.myApplication.requestCameraPermission();
More info about implementation here.

capture permission granted complete event

I'm creating an app using flash cc. I needed storage permission. It turns out I needed to ask user the permission for using storage devices. I can ask user for permission and it is working fine. I use examples from this website: https://helpx.adobe.com/flash-player/release-note/fp_24_air_24_release_notes.html
But my problem is I wasn't able to capture the complete event for accessing the storage permission. Because of that I couldn't run codes after I get access to storage. Is it possible to capture complete event for granting any permission?
the code I used:
var file:File = File.documentsDirectory.resolvePath("somefile.txt");
trace("url_txt:" + file.url);
file.addEventListener(PermissionEvent.PERMISSION_STATUS, function permissionStatusHandler(e:PermissionEvent):void
{
file.removeEventListener(PermissionEvent.PERMISSION_STATUS, permissionStatusHandler);
if(e.status == PermissionStatus.GRANTED)
{
myTextLoader.load(new URLRequest(file.url));
myTextLoader.addEventListener(Event.COMPLETE, onLoadTextComp);
myTextLoader.addEventListener(IOErrorEvent.IO_ERROR, loadingTextError);
}
else
{
showPermissionError();
}
}
);
try
{
trace("Requesting permission");
file.requestPermission();
}
catch(error:Error)
{
trace("Request permission error");
}
UPDATE:
The above code seems to work fine. But the problem occurs when I tried to request for same permission twice at different time. I've another question. Can we add description while we request permission? A lot of app seems to be adding description why the app need that particular permission. Is it possible do achieve this from flash as3? I've looked into web but couldn't find anything. And how to request permission for READ_PHONE_STATE?
Finally, I figured it out. Prerequisites:
AIR runtime 24+
Android 6+
APK must be published with WRITE_EXTERNAL_STORAGE permission (otherwise it is automatically DENIED - that what I was stuck at)
Then, this code works for me just fine, it displays Android's "Grant Permission" dialog and then outputs GRANTED or DENIED with regard to my choice. The Log class is just a debug panel of my own, you can change Log.log calls to trace or grab it here (it has no dependencies): https://bitbucket.org/thydmitry/ru.delimiter/src/9083fb46ce1c/classes/ru/delimiter/utils/
package
{
import ru.delimiter.utils.Log;
import flash.filesystem.File;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.PermissionEvent;
import flash.permissions.PermissionStatus;
public class Permissions extends Sprite
{
private var F:File;
public function Permissions()
{
if (stage) onStage();
else addEventListener(Event.ADDED_TO_STAGE, onStage);
}
private function onStage(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, onStage);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
Log.create(this, true);
Log.log("[Permissions Test] started");
Log.log("File.permissionStatus:", File.permissionStatus);
F = File.applicationStorageDirectory.resolvePath("somefile.txt");
F.addEventListener(PermissionEvent.PERMISSION_STATUS, onPerm);
stage.addEventListener(MouseEvent.CLICK, onClick);
}
private function onClick(e:MouseEvent):void
{
F.requestPermission();
}
private function onPerm(e:PermissionEvent):void
{
Log.log("User's decision:", e.status.toUpperCase());
}
}
}

Permission issue on runnable android

I'm trying to make a service that will run every 10 seconds to check a queue and if there is some data in the queue upload the given uri (uri of a picture just taken from the app) to a certain url.
I have created something along these lines
public QueueProcessor(final Context context){
this.mContext = context.getApplicationContext();
}
/**
* Starts processing the items on a separate thread.
*/
public void process() {
Runnable running = new Runnable() {
#Override
public void run() {
try{
Log.i(RUN_QP_TAG, "Processing queue");
// more here
isRunning = true;
Queue theQ = Queue.getInstance();
if(theQ.getSize() > 0){
WorkQItem itm = theQ.pop();
if(itm.hasImage()){
pushImageUploadToProcess(itm);
}
}
} catch (Exception e) {
// TODO: handle exception
}
finally{
//also call the same runnable to call it at regular interval
handler.postDelayed(this, 10000);
}
}
};
new Thread(running).start();
}
The pushImageUploadToProcess takes the WorkQItem and tries to upload the image from the item (which is saved as String picUri) by opening the uri and writing the bytes. However I get a permissions denied exception when trying to open the picUri location.
MediaDocumentsProvider uri content://com.android.providers.media.documents/document/image%3A531 from pid=795, uid=10327 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()
How can I allow this Thread/Runnable to have access to the URI?
Note I have tried the upload directly from a button event and it does work.
I have the following permissions in the manifest:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
You should implement runtime permission, for that use below code
PermissionsManager.getInstance().requestAllManifestPermissionsIfNecessary(this, new PermissionsResultAction() {
#Override
public void onGranted() {
// Proceed with initialization
}
#Override
public void onDenied(String permission) {
// Notify the user that you need all of the permissions
}
});
add below dependency in your app gradle
compile 'com.anthonycr.grant:permissions:1.0'
It looks like the actual permission error is not the real issue. The real issue was the way I was choosing and picking the image.
I ended up using https://gist.github.com/Mariovc/f06e70ebe8ca52fbbbe2 this picker and it seemed to work without any issues.
I tweaked this code a little bit to suit my needs as any image picked from the camera needed to be saved too so that if there are several images being done and each is needed for later use the relative uri initially will get overwritten therefore you need a fixed one (e.g: to be saved).

Categories

Resources