It possible to run ADB inside android flutter application without root.
There is an application that did it and it works as expected:
https://github.com/nightmare-space/adb_tool
It is using ADB binary compiled for Android like we can see here:
https://github.com/nightmare-space/adb_tool/tree/main/assets/android
However I can't find how it works, even with the full source.
ADB is installed to a strange location as you can see:
I tried to replicate this but it doesn't work at all.
final deposit = (await getApplicationDocumentsDirectory()).path;
const program = '/data/data/com.example.my_app/files/usr/bin/adb';
ByteData data = await rootBundle.load('assets/android/adb');
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
var file = File(program);
file = await file.create(recursive: true);
file.writeAsBytes(bytes);
await Process.run('chmod', ['+x', program]);
var results = await Process.run(program, ['--version']); // Permission error.
setState(() {
_counter += 1;
textarea.text = results.stdout;
});
Related
I'm using device_info_plus library for Device uniqid.
Everything working fine till.
But now I'm facing weird issue.
All device id has been changed.
Like first they are getting as 240a75fXXXXXXXXX like this.
Now they are getting as RKQ1.21XXXXXX.XXX like this.
Also same device id founded on different device.
Same device id has been get from 5 different device.
This is issue is only on Android Devices. Everything working fine on iOS.
I'm using
device_info_plus: ^8.0.0
await _deviceInfo.androidInfo.then((value) {
device_imeino = value.id;
});
Whats wrong??
You can try out with this code :
device_info_plus: ^3.2.1
String? deviceId = '';
getDeviceID() async {
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
deviceId = androidInfo.androidId ?? '';
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
deviceId = iosInfo.identifierForVendor ?? '';
}
}
androidID is removed from device_info_plus since v4.1.0. Check the changelog.
android_id package is recommanded to get the correct androidId.
I'm trying get a list of all documents in a device without breaking the illusion of being in my app like file picker does. I test with my physical device and it was working before but since I bought a better device using Android 11, I've been having issues.
This is what I did back then that worked. After getting permission errors for trying to access /storage/emulated/0/Android, I think there has been a permission update so I am now temporarily avoiding that folder manually but still I couldn't see any files until I added .mp4 to the allowed extensions and then it showed only my videos.
List<File> bookList = [];
List<List<String>> bookPropertyList = [];
fetchBooks() async {
var rootPath = await ExternalPath.getExternalStorageDirectories();
var docPath = Directory(rootPath[0]);
var docassets = docPath.listSync(recursive: true, followLinks: true);
for (FileSystemEntity asset in docassets) {
if (asset is File) {
String name = basename(asset.path);
if (name.endsWith('.pdf') ||
name.endsWith('.PDF') ||
name.endsWith('.txt') ||
name.endsWith('.TXT') ||
name.endsWith('.doc') ||
name.endsWith('.DOC') ||
name.endsWith('.docx') ||
name.endsWith('.DOCX')) {
String size = await getSize(asset.statSync().size, 1);
int day = asset.statSync().modified.day;
int month = asset.statSync().modified.month;
int year = asset.statSync().modified.year;
String date = '$day/$month/$year';
List<String> docProperty = [name, size, date];
bookPropertyList.add(docProperty);
File? file = asset.absolute;
bookList.add(file);
}
}
}
}
Is there a reason why it wouldn't show my files?
And is there a better way to get the files without leaving the app? Perhaps a light package I could use.
I have a small MAUI app i'm testing with. Im trying to read a file that was part of the deployment. I have the code below, which works great in a Windows deploy of the MAUI app, but crashes in Android. What is the proper cross-platform way to do this?
// TODO get from service or xml
var path = AppDomain.CurrentDomain.BaseDirectory;
//var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
var fullpath = Path.Combine(path, "Services\\questions.json");
var json = File.ReadAllText(fullpath);
MAUI has a new way to access files included with the app: MauiAsset.
Described in blog Announcing .NET MAUI Preview 4, Raw Assets:
.NET MAUI now makes it very easy to add other assets to your project and reference them directly while retaining platform-native performance. For example, if you want to display a static HTML file in a WebView you can add the file to your project and annotate it as a MauiAsset in the properties.
<MauiAsset Include="Resources\Raw\index.html" />
Tip: you can also use wildcards to enable all files in a directory:
... Include="Resources\Raw\*" ...
Then you can use it in your application by filename.
<WebView Source="index.html" />
UPDATE
However, the feature MauiAsset apparently still needs improvement:
open issue - MauiAsset is very hard to use.
There we learn that for now:
Set BuildAction in each file's properties to MauiAsset.
That is, its not recommended to use the "wildcard" approach at this time. Set that build action on each file in solution explorer / your project / the file.
Accessing on Windows requires a work-around:
#if WINDOWS
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync("Assets/" + filePath);
#else
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync(filePath);
#endif
NOTE: This will be simplified at some point; follow that issue to see progress.
UPDATE
The current MAUI template is missing some platform-specific flags. For now, add your own flag to identify when the code is running on Windows:
Complete example in ToolmakerSteve - repo MauiSOAnswers. See MauiAssetPage.
MauiAssetPage.xaml:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiTests.MauiAssetPage">
<ContentPage.Content>
<!-- By the time Maui is released, this is all you will need. -->
<!-- The Init code-behind won't be needed. -->
<WebView x:Name="MyWebView" Source="TestWeb.html" />
</ContentPage.Content>
</ContentPage>
MauiAssetPage.xaml.cs:
using Microsoft.Maui.Controls;
using System.Threading.Tasks;
namespace MauiTests
{
public partial class MauiAssetPage : ContentPage
{
public MauiAssetPage()
{
InitializeComponent();
Device.BeginInvokeOnMainThread(async () =>
{
await InitAsync();
});
}
private async Task InitAsync()
{
string filePath = "TestWeb.html";
#if WINDOWS
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync("Assets/" + filePath);
#else
var stream = await Microsoft.Maui.Essentials.FileSystem.OpenAppPackageFileAsync(filePath);
#endif
if (stream != null)
{
string s = (new System.IO.StreamReader(stream)).ReadToEnd();
this.MyWebView.Source = new HtmlWebViewSource { Html = s };
}
}
}
}
TestWeb.html:
(whatever html you want)
In Solution Explorer, add TestWeb.html to your project. In its Properties, select Build Action = MauiAsset.
I tried looking for a solution to this for months. I ended up hosting the file online then creating a method to download the file during runtime
public async Task DownloadFile(string fileName)
{
if (File.Exists(FileSystem.Current.AppDataDirectory + $"/{fileName}"))
{
return;
}
else
{
try
{
NetworkAccess networkAccess = Connectivity.Current.NetworkAccess;
if (networkAccess == NetworkAccess.Internet)
{
await Task.Run(() =>
{
var uri = new Uri($"https://myhostedfile.com/{fileName}");
WebClient webClient = new WebClient();
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCallback2);//checking if download is complete
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MaintainProgress);//event handler to check download progress
webClient.DownloadFileAsync(uri, FileSystem.Current.AppDataDirectory + $"/{fileName}");
});
}
else
await Shell.Current.DisplayAlert("No Internet", "Failed to get some files from the internet, confirm if your internet is" +
"working", "OK");
}
catch (Exception)
{
await Shell.Current.DisplayAlert("Error", "Failed to get some files from the internet, confirm if your internet is" +
"working", "OK");
}
}
}
Then you can access your file URL using:
string filePath = FileSystem.Current.AppDataDirectory + $"/myfile.pdf;
Trying to send esp32 chip wifi credentials from android app (built with ionic). Using Ionic capacitor/bluetooth-le plug in to write to esp32, using the write function:
await BleClient.write(device.deviceId, GATT Service ID, Characteristic ID, textToDataView('wifi_ssid,wifi_password'));
Code for BleClient.write:
async write(deviceId: string, service: string, characteristic: string, value: DataView): Promise<void> {
service = validateUUID(service);
characteristic = validateUUID(characteristic);
return this.queue(async () => {
if (!value?.buffer) {
throw new Error('Invalid data.');
}
let writeValue: DataView | string = value;
if (Capacitor.getPlatform() !== 'web') {
// on native we can only write strings
writeValue = dataViewToHexString(value);
}
await BluetoothLe.write({
deviceId,
service,
characteristic,
value: writeValue,
});
});
}
How to pass wifi credentials as value argument to the write function so it's correctly received by ESP32?
On the ESP32 side, I'm using the wifi_prov_mgr example code, which in turn uses google protocol buffer (I'm very new to protobuf and don't really understand how it works). ESP uses wifi_config.c (Espressif wifi_provisioning component). When I send wifi credentials from the app using BleClient.write, it shows up in wifi_config.c as inbuf with value wifi_ssid,wifi_password:��Z�?�Z�?
Here's the relevant code for wifi_config.c:
esp_err_t wifi_prov_config_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
ESP_LOGI(TAG ,"Wifi config payload inbuf value: %s", inbuf);
WiFiConfigPayload *req;
WiFiConfigPayload resp;
esp_err_t ret;
req = wi_fi_config_payload__unpack(NULL, inlen, inbuf);
if (!req) {
ESP_LOGE(TAG, "Unable to unpack config data");
return ESP_ERR_INVALID_ARG;
}
I'm having a very hard time understanding how wi_fi_config_payload_unpack processes the inbuf argument. This is where the code gets into protobuf code generated by the proto files. The proto file for wifi config data looks like this:
message CmdSetConfig {
bytes ssid = 1;
bytes passphrase = 2;
bytes bssid = 3;
int32 channel = 4;
}
message WiFiConfigPayload {
WiFiConfigMsgType msg = 1;
oneof payload {
CmdGetStatus cmd_get_status = 10;
RespGetStatus resp_get_status = 11;
CmdSetConfig cmd_set_config = 12;
RespSetConfig resp_set_config = 13;
CmdApplyConfig cmd_apply_config = 14;
RespApplyConfig resp_apply_config = 15;
}
}
So my question is - how do I pass the wifi credentials in BleClient.write so it is correctly recognized by wifi_config.c on the ESP32 side?
I thought about using the Android app developed by Espressif to pass wifi credentials to the ESP32 chip, but then I don't know how to integrate native Android code with ionic code, since I need my app to do more than just credential the ESP32.
Resolved this issue by using Google's protocol buffer code for javascript. Here's code that worked for me:
import * as goog from 'google-protobuf';
export class SetupPage implements OnInit {
messages = require('../../assets/proto-js/wifi_config_pb.js');
connectedDevice: BleDevice;
bleScan: any;
wifiSSID: Promise<any>;
scanResults: Promise<[]>;
wifiCredentials: string;
uint8String: Uint8Array;
buffer: Uint8Array;
ngOnInit() {
async getBLE(){
await BleClient.initialize();
var cmdSetMessage = new this.messages.CmdSetConfig();
var wifiConfigPayloadMessage = new this.messages.WiFiConfigPayload;
message.setSsid('wifiid');
message.setPassphrase('password');
wifiConfigPayloadMessage.setCmdSetConfig(cmdSetMessage);
wifiConfigPayloadMessage.setMsg(2); //this is ESP32 BLE specific
let bytesOfStuff = await wifiConfigPayloadMessage.serializeBinary();
this.connectedDevice = await BleClient.requestDevice();
await BleClient.connect(this.connectedDevice.deviceId);
BleClient.writeWithoutResponse(this.connectedDevice.deviceId, '021a9004-0382-
4aea-bff4-6b3f1c5adfb4', '021aff52-0382-4aea-bff4-6b3f1c5adfb4',
bytesOfStuff)
//After this, you need to send wifiConfigPayloadMessage.setMsg(4) to ESP32 to apply wifi credentials and connect to the selected wifi
}
}
Before this code can function, you need to install google-protobuf from npm or other package installer and protoc or another compiler for the proto files in order to generate pb.js files (since I'm working with javascript) for each proto file. In this example, the proto file is called wifi_config.proto, and the corresponding pb.js file (created by running protoc) is called wifi_config_pb.js.
Functions used to create the data to be transferred are defined in the pb.js files and correspond to objects defined in the proto files. The following references will help a lot if you don't have a good understanding of how this mechanism works:
https://developers.google.com/protocol-buffers/docs/proto3 (google protocol-buffer tutorial)
Protocol Buffers in Ionic (stack question)
I am trying to pass an image as Uint8List to native code Android/iOS from Flutter, but getting an error in iOS side. I am modifying a plugin and i never developed in iOS before.
Here is the Flutter code to capture an image from widget and send the Uint8List to native code.
Future<void> _capturePng() async {
RenderRepaintBoundary boundary =
globalKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage(pixelRatio: 1.50);
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
printer.printImage(pngBytes);
}
In Android, i am using Kotlin:
private fun printImage(call: MethodCall, result: Result) {
val image = call.arguments as ByteArray
val res = mPrinterPlugin.printImage(image)
result.success(res)
}
In iOS, the plugin is written in Objective-C. i added this check for my image
else if([#"imagePrint" isEqualToString:call.method]){
NSData *imgBytes = call.arguments;
UIImage *label = [UIImage imageWithData:imgBytes];
}
I saw that Uint8List is FlutterStandardTypedData typedDataWithBytes in iOS, but when i set the imgBytes type to it i get an error.
You should take first argument as a typed data, probably:
NSArray *args = call.arguments;
FlutterStandardTypedData *list = args[0];
kotlin:
val args: List<Any> = call.arguments as List<Any>
val list = args[0] as ByteArray
and you invoke it like this:
Uint8List uint8List;
_channel.invokeMethod("your_function_name", [uint8List]);
I just dealt with this. Letting Xcode do its magical conversion was burning me. For instance, in your iOS plugin implementation, the line:
NSData *imgBytes = call.arguments;
When i was doing this, in Xcode the debugger would see the data type would come through as a FlutterStandardTypedData (as i passed in a list of ints) but every time i would try to access an element/object it would give me memory errors. It was like Xcode was honoring my parameter list (NSArray) in reality, but showing me the data type as a FlutterStandardTypedData while debugging. Seemed like a bug in the IDE to be honest.
Just as the upvoted answer shows, i resolved by using the flutter data type:
FlutterStandardTypedData *bytesList = call.arguments[#"listOfBytes"];