Compare commits
No commits in common. "42155d9e31bf103dc26a668b51e7dc9d4639232d" and "7a999cf86089f6756634a4f15afef93f68747918" have entirely different histories.
42155d9e31
...
7a999cf860
14
.gitlint
14
.gitlint
@ -1,14 +0,0 @@
|
||||
[general]
|
||||
ignore=B5,B6,B7,B8
|
||||
|
||||
[title-max-length]
|
||||
line-length=72
|
||||
|
||||
[title-trailing-punctuation]
|
||||
[title-hard-tab]
|
||||
[title-match-regex]
|
||||
regex=^(feat|fix|chore|refactor)\((android|ios|linux|windows|macos|interface|base|repo)(,(android|ios|linux|windows|macos|interface|base))*\): [A-Z0-9].*$
|
||||
|
||||
|
||||
[body-trailing-whitespace]
|
||||
[body-first-line-empty]
|
278
CHANGELOG.md
278
CHANGELOG.md
@ -1,278 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## 2023-09-03
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform_android` - `v0.1.22`](#moxplatform_android---v0122)
|
||||
- [`moxplatform_platform_interface` - `v0.1.22`](#moxplatform_platform_interface---v0122)
|
||||
- [`moxplatform` - `v0.1.17+6`](#moxplatform---v01176)
|
||||
|
||||
Packages with dependency updates only:
|
||||
|
||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||
|
||||
- `moxplatform` - `v0.1.17+6`
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform_android` - `v0.1.22`
|
||||
|
||||
- **FIX**(android): Fix notification grouping with the foreground notification.
|
||||
- **FIX**(android): Fix creating notifications on Android 13.
|
||||
- **FEAT**(interface,android): Allow passing an initial locale to the service.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
#### `moxplatform_platform_interface` - `v0.1.22`
|
||||
|
||||
- **FIX**(repo): Remove notification examples.
|
||||
- **FEAT**(interface,android): Allow passing an initial locale to the service.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
|
||||
## 2023-09-03
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform_android` - `v0.1.21`](#moxplatform_android---v0121)
|
||||
- [`moxplatform_platform_interface` - `v0.1.21`](#moxplatform_platform_interface---v0121)
|
||||
- [`moxplatform` - `v0.1.17+5`](#moxplatform---v01175)
|
||||
|
||||
Packages with dependency updates only:
|
||||
|
||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||
|
||||
- `moxplatform` - `v0.1.17+5`
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform_android` - `v0.1.21`
|
||||
|
||||
- **FIX**(android): Fix notification grouping with the foreground notification.
|
||||
- **FIX**(android): Fix creating notifications on Android 13.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
#### `moxplatform_platform_interface` - `v0.1.21`
|
||||
|
||||
- **FIX**(repo): Remove notification examples.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
|
||||
## 2023-08-05
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform_android` - `v0.1.20`](#moxplatform_android---v0120)
|
||||
- [`moxplatform_platform_interface` - `v0.1.20`](#moxplatform_platform_interface---v0120)
|
||||
- [`moxplatform` - `v0.1.17+4`](#moxplatform---v01174)
|
||||
|
||||
Packages with dependency updates only:
|
||||
|
||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||
|
||||
- `moxplatform` - `v0.1.17+4`
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform_android` - `v0.1.20`
|
||||
|
||||
- **FIX**(android): Fix FileProvider id.
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
#### `moxplatform_platform_interface` - `v0.1.20`
|
||||
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
|
||||
## 2023-08-05
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform_android` - `v0.1.19`](#moxplatform_android---v0119)
|
||||
- [`moxplatform_platform_interface` - `v0.1.19`](#moxplatform_platform_interface---v0119)
|
||||
- [`moxplatform` - `v0.1.17+3`](#moxplatform---v01173)
|
||||
|
||||
Packages with dependency updates only:
|
||||
|
||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||
|
||||
- `moxplatform` - `v0.1.17+3`
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform_android` - `v0.1.19`
|
||||
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
#### `moxplatform_platform_interface` - `v0.1.19`
|
||||
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
|
||||
## 2023-08-04
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform` - `v0.1.17+2`](#moxplatform---v01172)
|
||||
- [`moxplatform_android` - `v0.1.18`](#moxplatform_android---v0118)
|
||||
- [`moxplatform_platform_interface` - `v0.1.18`](#moxplatform_platform_interface---v0118)
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform` - `v0.1.17+2`
|
||||
|
||||
- **FIX**: Format and lint.
|
||||
|
||||
#### `moxplatform_android` - `v0.1.18`
|
||||
|
||||
- **FIX**: Format and lint.
|
||||
- **FIX**: Fix self-replies after receiving another message.
|
||||
- **FIX**: Add payload to all intents.
|
||||
- **FIX**: Fix images disappearing after replying.
|
||||
- **FEAT**: Move recordSentMessage to pigeon.
|
||||
- **FEAT**: Move the crypto APIs to pigeon.
|
||||
- **FEAT**: Adjust to Moxxy changes.
|
||||
- **FEAT**: Store the avatar path also in the shared preferences.
|
||||
- **FEAT**: Allow the sender's data being null.
|
||||
- **FEAT**: Allow attaching arbitrary data to the notification.
|
||||
- **FEAT**: Allow showing regular notifications.
|
||||
- **FEAT**: Make i18n data a bit more persistent.
|
||||
- **FEAT**: Color in the notification silhouette.
|
||||
- **FEAT**: Allow setting the self-avatar.
|
||||
- **FEAT**: Take care of i18n.
|
||||
|
||||
#### `moxplatform_platform_interface` - `v0.1.18`
|
||||
|
||||
- **FIX**: Format and lint.
|
||||
- **FIX**: Add payload to all intents.
|
||||
- **FEAT**: Move recordSentMessage to pigeon.
|
||||
- **FEAT**: Move the crypto APIs to pigeon.
|
||||
- **FEAT**: Allow the sender's data being null.
|
||||
- **FEAT**: Allow attaching arbitrary data to the notification.
|
||||
- **FEAT**: Allow showing regular notifications.
|
||||
- **FEAT**: Color in the notification silhouette.
|
||||
- **FEAT**: Allow setting the self-avatar.
|
||||
- **FEAT**: Take care of i18n.
|
||||
|
||||
|
||||
## 2023-07-21
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform_android` - `v0.1.17+1`](#moxplatform_android---v01171)
|
||||
- [`moxplatform` - `v0.1.17+1`](#moxplatform---v01171)
|
||||
- [`moxplatform_platform_interface` - `v0.1.17+1`](#moxplatform_platform_interface---v01171)
|
||||
|
||||
Packages with dependency updates only:
|
||||
|
||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||
|
||||
- `moxplatform` - `v0.1.17+1`
|
||||
- `moxplatform_platform_interface` - `v0.1.17+1`
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform_android` - `v0.1.17+1`
|
||||
|
||||
- **FIX**: Accidentally used the name as the target's key. Oops.
|
||||
- **FIX**: Fix minor things.
|
||||
|
||||
|
||||
## 2023-07-21
|
||||
|
||||
### Changes
|
||||
|
||||
---
|
||||
|
||||
Packages with breaking changes:
|
||||
|
||||
- There are no breaking changes in this release.
|
||||
|
||||
Packages with other changes:
|
||||
|
||||
- [`moxplatform` - `v0.1.17`](#moxplatform---v0117)
|
||||
- [`moxplatform_android` - `v0.1.17`](#moxplatform_android---v0117)
|
||||
- [`moxplatform_platform_interface` - `v0.1.17`](#moxplatform_platform_interface---v0117)
|
||||
|
||||
---
|
||||
|
||||
#### `moxplatform` - `v0.1.17`
|
||||
|
||||
- **FIX**: Fix typecasting issue.
|
||||
- **FEAT**: Add an API for creating direct share shortcuts.
|
||||
- **FEAT**: Migrate to moxlib 0.2.0.
|
||||
- **FEAT**: I forgot to bump dependency versions.
|
||||
|
||||
#### `moxplatform_android` - `v0.1.17`
|
||||
|
||||
- **FIX**: Fix typecasting issue.
|
||||
- **FEAT**: Improve code quality of the cryptography.
|
||||
- **FEAT**: Rewrite recordSentMessage in Kotlin.
|
||||
- **FEAT**: Add an API for creating direct share shortcuts.
|
||||
- **FEAT**: Migrate to moxlib 0.2.0.
|
||||
- **FEAT**: I forgot to bump dependency versions.
|
||||
- **FEAT**: Also hash the file on encryption and decryption.
|
||||
|
||||
#### `moxplatform_platform_interface` - `v0.1.17`
|
||||
|
||||
- **FIX**: Fix typecasting issue.
|
||||
- **FEAT**: Add an API for creating direct share shortcuts.
|
||||
- **FEAT**: Migrate to moxlib 0.2.0.
|
||||
- **FEAT**: I forgot to bump dependency versions.
|
||||
- **FEAT**: Also hash the file on encryption and decryption.
|
||||
|
@ -9,11 +9,9 @@ This repo is based on [very_good_flutter_plugin](https://github.com/VeryGoodOpen
|
||||
The development of this package is based on [melos](https://pub.dev/packages/melos).
|
||||
|
||||
To make all packages link to each other locally, begin by running `melos bootstrap`. After editing
|
||||
the code and making your changes, please format the code using `melos run format` and lint using `melos run analyze`.
|
||||
|
||||
When done - and a version bump is appropriate - bump the version of all packages using `melos version` and
|
||||
publish with `melos publish --no-dry-run --git-tag-version`.
|
||||
the code and making your changes, please run `melos run analyze` to make sure that no linter warnings
|
||||
are left inside the code.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
- [ekasetiawans](https://github.com/ekasetiawans) for [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). moxplatform_android's service implementation is basically just a copy and paste of [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service).
|
||||
- [ekasetiawans](https://github.com/ekasetiawans) for [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service). moxplatform_android is basically just a copy and paste of [flutter_background_service](https://github.com/ekasetiawans/flutter_background_service).
|
||||
|
@ -1,8 +1,4 @@
|
||||
include: package:very_good_analysis/analysis_options.yaml
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/src/api.g.dart
|
||||
|
||||
linter:
|
||||
rules:
|
||||
public_member_api_docs: false
|
||||
|
@ -47,7 +47,7 @@ android {
|
||||
applicationId "com.example.example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||
minSdkVersion 26
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -23,8 +23,8 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
@ -34,5 +34,5 @@
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
</manifest>
|
||||
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.8.21'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
@ -26,6 +26,6 @@ subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
@ -3,60 +3,59 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
/// The id of the notification channel.
|
||||
const channelId = "me.polynom.moxplatform.testing3";
|
||||
const otherChannelId = "me.polynom.moxplatform.testing4";
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class Sender {
|
||||
const Sender(this.name, this.jid);
|
||||
|
||||
final String name;
|
||||
|
||||
final String jid;
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
MyAppState createState() => MyAppState();
|
||||
}
|
||||
|
||||
class MyAppState extends State<MyApp> {
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Moxplatform Demo',
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key});
|
||||
const MyHomePage({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
MyHomePageState createState() => MyHomePageState();
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class MyHomePageState extends State<MyHomePage> {
|
||||
/// List of "Message senders".
|
||||
final List<Sender> senders = const [
|
||||
Sender('Mash Kyrielight', 'mash@example.org'),
|
||||
Sender('Rio Tsukatsuki', 'rio@millenium'),
|
||||
Sender('Raiden Shogun', 'raiden@tevhat'),
|
||||
];
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
Future<void> _cryptoTest() async {
|
||||
Future<void> _incrementCounter() async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result == null) {
|
||||
return;
|
||||
@ -66,7 +65,7 @@ class MyHomePageState extends State<MyHomePage> {
|
||||
final path = result.files.single.path;
|
||||
final enc = await MoxplatformPlugin.crypto.encryptFile(
|
||||
path!,
|
||||
'$path.enc',
|
||||
path + '.enc',
|
||||
Uint8List.fromList(List.filled(32, 1)),
|
||||
Uint8List.fromList(List.filled(16, 2)),
|
||||
CipherAlgorithm.aes256CbcPkcs7,
|
||||
@ -75,165 +74,75 @@ class MyHomePageState extends State<MyHomePage> {
|
||||
final end = DateTime.now();
|
||||
|
||||
final diff = end.millisecondsSinceEpoch - start.millisecondsSinceEpoch;
|
||||
// ignore: avoid_print
|
||||
print('TIME: ${diff / 1000}s');
|
||||
// ignore: avoid_print
|
||||
print('DONE (${enc != null})');
|
||||
final lengthEnc = await File('$path.enc').length();
|
||||
final lengthEnc = await File(path + ".enc").length();
|
||||
final lengthOrig = await File(path).length();
|
||||
// ignore: avoid_print
|
||||
print('Encrypted file is $lengthEnc Bytes large (Orig $lengthOrig)');
|
||||
|
||||
await MoxplatformPlugin.crypto.decryptFile(
|
||||
'$path.enc',
|
||||
'$path.dec',
|
||||
path + '.enc',
|
||||
path + '.dec',
|
||||
Uint8List.fromList(List.filled(32, 1)),
|
||||
Uint8List.fromList(List.filled(16, 2)),
|
||||
CipherAlgorithm.aes256CbcPkcs7,
|
||||
'SHA-256',
|
||||
);
|
||||
// ignore: avoid_print
|
||||
print('DONE');
|
||||
|
||||
final lengthDec = await File('$path.dec').length();
|
||||
// ignore: avoid_print
|
||||
final lengthDec = await File(path + ".dec").length();
|
||||
print('Decrypted file is $lengthDec Bytes large (Orig $lengthOrig)');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Moxplatform Demo'),
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: _cryptoTest,
|
||||
child: const Text('Test cryptography'),
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Invoke "debug painting" (press "p" in the console, choose the
|
||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||
// to see the wireframe for each widget.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
MoxplatformPlugin.contacts.recordSentMessage('Hallo', 'Welt');
|
||||
},
|
||||
child: const Text('Test recordSentMessage (no fallback)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
MoxplatformPlugin.contacts.recordSentMessage('Person', 'Person',
|
||||
fallbackIcon: FallbackIconType.person);
|
||||
},
|
||||
child: const Text('Test recordSentMessage (person fallback)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
MoxplatformPlugin.contacts.recordSentMessage('Notes', 'Notes',
|
||||
fallbackIcon: FallbackIconType.notes);
|
||||
},
|
||||
child: const Text('Test recordSentMessage (notes fallback)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
// ignore: avoid_print
|
||||
print(await MoxplatformPlugin.platform.getPersistentDataPath());
|
||||
},
|
||||
child: const Text('Get data directory'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
// ignore: avoid_print
|
||||
print(await MoxplatformPlugin.platform.getCacheDataPath());
|
||||
},
|
||||
child: const Text('Get cache directory'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
// ignore: avoid_print
|
||||
print(await MoxplatformPlugin.platform
|
||||
.isIgnoringBatteryOptimizations());
|
||||
},
|
||||
child: const Text('Is battery optimised?'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await MoxplatformPlugin.platform
|
||||
.openBatteryOptimisationSettings();
|
||||
},
|
||||
child: const Text('Open battery optimisation page'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.video,
|
||||
);
|
||||
if (result == null) return;
|
||||
|
||||
final path = result.files.single.path!;
|
||||
final storagePath =
|
||||
await MoxplatformPlugin.platform.getPersistentDataPath();
|
||||
final mediaPath = join(storagePath, 'media');
|
||||
if (!Directory(mediaPath).existsSync()) {
|
||||
await Directory(mediaPath).create(recursive: true);
|
||||
}
|
||||
|
||||
final internalPath = join(mediaPath, basename(path));
|
||||
// ignore: avoid_print
|
||||
print('Copying file');
|
||||
await File(path).copy(internalPath);
|
||||
|
||||
// ignore: avoid_print
|
||||
print('Generating thumbnail');
|
||||
final thumbResult =
|
||||
await MoxplatformPlugin.platform.generateVideoThumbnail(
|
||||
internalPath,
|
||||
'$internalPath.thumbnail.jpg',
|
||||
720,
|
||||
);
|
||||
// ignore: avoid_print
|
||||
print('Success: $thumbResult');
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => Image.file(
|
||||
File('$internalPath.thumbnail.jpg'),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Thumbnail'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.image, false);
|
||||
print('Picked files $result');
|
||||
},
|
||||
child: const Text('Pick image'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.image, true);
|
||||
print('Picked files $result');
|
||||
},
|
||||
child: const Text('Pick multiple images'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.imageAndVideo, true);
|
||||
print('Picked files $result');
|
||||
},
|
||||
child: const Text('Pick multiple images and videos'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await MoxplatformPlugin.platform.pickFiles(FilePickerType.generic, true);
|
||||
print('Picked files $result');
|
||||
},
|
||||
child: const Text('Pick multiple generic files'),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,12 @@ dependencies:
|
||||
|
||||
moxplatform:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.17+6
|
||||
version: 0.1.15
|
||||
moxplatform_android:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.22
|
||||
version: 0.1.15
|
||||
|
||||
file_picker: 5.2.0+1
|
||||
|
||||
path: 1.8.3
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
117
flake.lock
117
flake.lock
@ -1,66 +1,6 @@
|
||||
{
|
||||
"nodes": {
|
||||
"android-nixpkgs": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689798050,
|
||||
"narHash": "sha256-ZyFPra7N0MF803o55dYQQyX9b/BmXr6QTCyN7slRThY=",
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"rev": "9aa0e2990da86de8ca203af313668851dcb9ea6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"android-nixpkgs",
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688380630,
|
||||
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1649676176,
|
||||
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||
@ -77,27 +17,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1689679375,
|
||||
"narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=",
|
||||
"lastModified": 1660551188,
|
||||
"narHash": "sha256-a1LARMMYQ8DPx1BgoI/UN4bXe12hhZkCNqdxNi6uS0g=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "684c17c429c42515bafb3ad775d2a710947f3d67",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1689752456,
|
||||
"narHash": "sha256-VOChdECcEI8ixz8QY+YC4JaNEFwQd1V8bA0G4B28Ki0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7f256d7da238cb627ef189d56ed590739f42f13b",
|
||||
"rev": "441dc5d512153039f19ef198e662e4f3dbb9fd65",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -109,39 +33,8 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"android-nixpkgs": "android-nixpkgs",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
54
flake.nix
54
flake.nix
@ -3,10 +3,9 @@
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
android-nixpkgs.url = "github:tadfisher/android-nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, android-nixpkgs }: flake-utils.lib.eachDefaultSystem (system: let
|
||||
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config = {
|
||||
@ -14,32 +13,29 @@
|
||||
allowUnfree = true;
|
||||
};
|
||||
};
|
||||
# Everything to make Flutter happy
|
||||
sdk = android-nixpkgs.sdk.${system} (sdkPkgs: with sdkPkgs; [
|
||||
cmdline-tools-latest
|
||||
build-tools-30-0-3
|
||||
build-tools-33-0-2
|
||||
build-tools-34-0-0
|
||||
platform-tools
|
||||
emulator
|
||||
patcher-v4
|
||||
platforms-android-30
|
||||
platforms-android-31
|
||||
platforms-android-33
|
||||
]);
|
||||
pinnedJDK = pkgs.jdk17;
|
||||
android = pkgs.androidenv.composeAndroidPackages {
|
||||
# TODO: Find a way to pin these
|
||||
#toolsVersion = "26.1.1";
|
||||
#platformToolsVersion = "31.0.3";
|
||||
#buildToolsVersions = [ "31.0.0" ];
|
||||
#includeEmulator = true;
|
||||
#emulatorVersion = "30.6.3";
|
||||
platformVersions = [ "28" ];
|
||||
includeSources = false;
|
||||
includeSystemImages = true;
|
||||
systemImageTypes = [ "default" ];
|
||||
abiVersions = [ "x86_64" ];
|
||||
includeNDK = false;
|
||||
useGoogleAPIs = false;
|
||||
useGoogleTVAddOns = false;
|
||||
};
|
||||
pinnedJDK = pkgs.jdk;
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
# Android
|
||||
pinnedJDK
|
||||
sdk
|
||||
|
||||
# Flutter
|
||||
flutter dart
|
||||
|
||||
# Code hygiene
|
||||
gitlint
|
||||
flutter pinnedJDK android.platform-tools dart # Flutter
|
||||
gitlint jq # Code hygiene
|
||||
ripgrep # General utilities
|
||||
|
||||
# Flutter dependencies for linux desktop
|
||||
atk
|
||||
@ -63,13 +59,9 @@
|
||||
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
||||
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
|
||||
|
||||
ANDROID_HOME = "${sdk}/share/android-sdk";
|
||||
ANDROID_SDK_ROOT = "${sdk}/share/android-sdk";
|
||||
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||
JAVA_HOME = pinnedJDK;
|
||||
|
||||
# Fix an issue with Flutter using an older version of aapt2, which does not know
|
||||
# an used parameter.
|
||||
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk}/share/android-sdk/build-tools/34.0.0/aapt2";
|
||||
ANDROID_AVD_HOME = (toString ./.) + "/.android/avd";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -8,7 +8,5 @@ command:
|
||||
usePubspecOverrides: true
|
||||
|
||||
scripts:
|
||||
format:
|
||||
exec: dart format .
|
||||
analyze:
|
||||
exec: flutter analyze
|
||||
exec: dart analyze .
|
||||
|
@ -1,34 +1,3 @@
|
||||
## 0.1.17+6
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
||||
## 0.1.17+5
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
||||
## 0.1.17+4
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
||||
## 0.1.17+3
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
||||
## 0.1.17+2
|
||||
|
||||
- **FIX**: Format and lint.
|
||||
|
||||
## 0.1.17+1
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
||||
## 0.1.17
|
||||
|
||||
- **FIX**: Fix typecasting issue.
|
||||
- **FEAT**: Add an API for creating direct share shortcuts.
|
||||
- **FEAT**: Migrate to moxlib 0.2.0.
|
||||
- **FEAT**: I forgot to bump dependency versions.
|
||||
|
||||
## 0.1.11+2
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
@ -1,6 +1,4 @@
|
||||
library moxplatform;
|
||||
|
||||
export 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
export 'src/plugin.dart';
|
||||
export 'src/types.dart';
|
||||
|
@ -2,7 +2,6 @@ import 'package:moxplatform_platform_interface/moxplatform_platform_interface.da
|
||||
|
||||
class MoxplatformPlugin {
|
||||
static IsolateHandler get handler => MoxplatformInterface.handler;
|
||||
static MediaScannerImplementation get media => MoxplatformInterface.media;
|
||||
static CryptographyImplementation get crypto => MoxplatformInterface.crypto;
|
||||
static ContactsImplementation get contacts => MoxplatformInterface.contacts;
|
||||
static PlatformImplementation get platform => MoxplatformInterface.platform;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxlib/awaitabledatasender.dart';
|
||||
|
||||
abstract class BackgroundCommand implements JsonImplementation {}
|
||||
|
||||
abstract class BackgroundEvent implements JsonImplementation {}
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,11 +1,11 @@
|
||||
name: moxplatform
|
||||
description: Moxxy platform-specific code
|
||||
version: 0.1.17+6
|
||||
version: 0.1.15
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
homepage: https://codeberg.org/moxxy/moxplatform
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
sdk: ">=2.16.0 <3.0.0"
|
||||
flutter: ">=2.10.0"
|
||||
|
||||
flutter:
|
||||
@ -22,17 +22,17 @@ dependencies:
|
||||
|
||||
moxlib:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.2.0
|
||||
version: ^0.1.4
|
||||
|
||||
moxplatform_android:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.1.22
|
||||
version: ^0.1.15
|
||||
moxplatform_platform_interface:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.1.22
|
||||
version: ^0.1.15
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.1.2
|
||||
very_good_analysis: ^3.0.1
|
||||
very_good_analysis: ^2.4.0
|
||||
|
@ -1 +0,0 @@
|
||||
pubspec_overrides.yaml
|
@ -1,60 +1,3 @@
|
||||
## 0.1.22
|
||||
|
||||
- **FIX**(android): Fix notification grouping with the foreground notification.
|
||||
- **FIX**(android): Fix creating notifications on Android 13.
|
||||
- **FEAT**(interface,android): Allow passing an initial locale to the service.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
## 0.1.21
|
||||
|
||||
- **FIX**(android): Fix notification grouping with the foreground notification.
|
||||
- **FIX**(android): Fix creating notifications on Android 13.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
## 0.1.20
|
||||
|
||||
- **FIX**(android): Fix FileProvider id.
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
## 0.1.19
|
||||
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
## 0.1.18
|
||||
|
||||
- **FIX**: Format and lint.
|
||||
- **FIX**: Fix self-replies after receiving another message.
|
||||
- **FIX**: Add payload to all intents.
|
||||
- **FIX**: Fix images disappearing after replying.
|
||||
- **FEAT**: Move recordSentMessage to pigeon.
|
||||
- **FEAT**: Move the crypto APIs to pigeon.
|
||||
- **FEAT**: Adjust to Moxxy changes.
|
||||
- **FEAT**: Store the avatar path also in the shared preferences.
|
||||
- **FEAT**: Allow the sender's data being null.
|
||||
- **FEAT**: Allow attaching arbitrary data to the notification.
|
||||
- **FEAT**: Allow showing regular notifications.
|
||||
- **FEAT**: Make i18n data a bit more persistent.
|
||||
- **FEAT**: Color in the notification silhouette.
|
||||
- **FEAT**: Allow setting the self-avatar.
|
||||
- **FEAT**: Take care of i18n.
|
||||
|
||||
## 0.1.17+1
|
||||
|
||||
- **FIX**: Accidentally used the name as the target's key. Oops.
|
||||
- **FIX**: Fix minor things.
|
||||
|
||||
## 0.1.17
|
||||
|
||||
- **FIX**: Fix typecasting issue.
|
||||
- **FEAT**: Improve code quality of the cryptography.
|
||||
- **FEAT**: Rewrite recordSentMessage in Kotlin.
|
||||
- **FEAT**: Add an API for creating direct share shortcuts.
|
||||
- **FEAT**: Migrate to moxlib 0.2.0.
|
||||
- **FEAT**: I forgot to bump dependency versions.
|
||||
- **FEAT**: Also hash the file on encryption and decryption.
|
||||
|
||||
## 0.1.11+2
|
||||
|
||||
- **REFACTOR**: Make version constraints looser.
|
||||
|
@ -2,17 +2,13 @@ group 'me.polynom.moxplatform_android'
|
||||
version '1.0'
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.8.21'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,10 +20,9 @@ rootProject.allprojects {
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@ -35,14 +30,10 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// What Moxxy currently uses
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
minSdkVersion 16
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.core:core:1.10.1'
|
||||
implementation 'androidx.activity:activity:1.7.2'
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -1,10 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.polynom.moxplatform_android">
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
|
||||
<application>
|
||||
@ -28,5 +26,6 @@
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
@ -1,507 +0,0 @@
|
||||
// Autogenerated from Pigeon (v10.1.4), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
package me.polynom.moxplatform_android;
|
||||
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.plugin.common.BasicMessageChannel;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MessageCodec;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Generated class from Pigeon. */
|
||||
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
|
||||
public class Api {
|
||||
|
||||
/** Error class for passing custom error details to Flutter via a thrown PlatformException. */
|
||||
public static class FlutterError extends RuntimeException {
|
||||
|
||||
/** The error code. */
|
||||
public final String code;
|
||||
|
||||
/** The error details. Must be a datatype supported by the api codec. */
|
||||
public final Object details;
|
||||
|
||||
public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details)
|
||||
{
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected static ArrayList<Object> wrapError(@NonNull Throwable exception) {
|
||||
ArrayList<Object> errorList = new ArrayList<Object>(3);
|
||||
if (exception instanceof FlutterError) {
|
||||
FlutterError error = (FlutterError) exception;
|
||||
errorList.add(error.code);
|
||||
errorList.add(error.getMessage());
|
||||
errorList.add(error.details);
|
||||
} else {
|
||||
errorList.add(exception.toString());
|
||||
errorList.add(exception.getClass().getSimpleName());
|
||||
errorList.add(
|
||||
"Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
public enum CipherAlgorithm {
|
||||
AES128GCM_NO_PADDING(0),
|
||||
AES256GCM_NO_PADDING(1),
|
||||
AES256CBC_PKCS7(2);
|
||||
|
||||
final int index;
|
||||
|
||||
private CipherAlgorithm(final int index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FallbackIconType {
|
||||
NONE(0),
|
||||
PERSON(1),
|
||||
NOTES(2);
|
||||
|
||||
final int index;
|
||||
|
||||
private FallbackIconType(final int index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FilePickerType {
|
||||
IMAGE(0),
|
||||
VIDEO(1),
|
||||
IMAGE_AND_VIDEO(2),
|
||||
GENERIC(3);
|
||||
|
||||
final int index;
|
||||
|
||||
private FilePickerType(final int index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
public static final class CryptographyResult {
|
||||
private @NonNull byte[] plaintextHash;
|
||||
|
||||
public @NonNull byte[] getPlaintextHash() {
|
||||
return plaintextHash;
|
||||
}
|
||||
|
||||
public void setPlaintextHash(@NonNull byte[] setterArg) {
|
||||
if (setterArg == null) {
|
||||
throw new IllegalStateException("Nonnull field \"plaintextHash\" is null.");
|
||||
}
|
||||
this.plaintextHash = setterArg;
|
||||
}
|
||||
|
||||
private @NonNull byte[] ciphertextHash;
|
||||
|
||||
public @NonNull byte[] getCiphertextHash() {
|
||||
return ciphertextHash;
|
||||
}
|
||||
|
||||
public void setCiphertextHash(@NonNull byte[] setterArg) {
|
||||
if (setterArg == null) {
|
||||
throw new IllegalStateException("Nonnull field \"ciphertextHash\" is null.");
|
||||
}
|
||||
this.ciphertextHash = setterArg;
|
||||
}
|
||||
|
||||
/** Constructor is non-public to enforce null safety; use Builder. */
|
||||
CryptographyResult() {}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable byte[] plaintextHash;
|
||||
|
||||
public @NonNull Builder setPlaintextHash(@NonNull byte[] setterArg) {
|
||||
this.plaintextHash = setterArg;
|
||||
return this;
|
||||
}
|
||||
|
||||
private @Nullable byte[] ciphertextHash;
|
||||
|
||||
public @NonNull Builder setCiphertextHash(@NonNull byte[] setterArg) {
|
||||
this.ciphertextHash = setterArg;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CryptographyResult build() {
|
||||
CryptographyResult pigeonReturn = new CryptographyResult();
|
||||
pigeonReturn.setPlaintextHash(plaintextHash);
|
||||
pigeonReturn.setCiphertextHash(ciphertextHash);
|
||||
return pigeonReturn;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
ArrayList<Object> toList() {
|
||||
ArrayList<Object> toListResult = new ArrayList<Object>(2);
|
||||
toListResult.add(plaintextHash);
|
||||
toListResult.add(ciphertextHash);
|
||||
return toListResult;
|
||||
}
|
||||
|
||||
static @NonNull CryptographyResult fromList(@NonNull ArrayList<Object> list) {
|
||||
CryptographyResult pigeonResult = new CryptographyResult();
|
||||
Object plaintextHash = list.get(0);
|
||||
pigeonResult.setPlaintextHash((byte[]) plaintextHash);
|
||||
Object ciphertextHash = list.get(1);
|
||||
pigeonResult.setCiphertextHash((byte[]) ciphertextHash);
|
||||
return pigeonResult;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Result<T> {
|
||||
@SuppressWarnings("UnknownNullness")
|
||||
void success(T result);
|
||||
|
||||
void error(@NonNull Throwable error);
|
||||
}
|
||||
|
||||
private static class MoxplatformApiCodec extends StandardMessageCodec {
|
||||
public static final MoxplatformApiCodec INSTANCE = new MoxplatformApiCodec();
|
||||
|
||||
private MoxplatformApiCodec() {}
|
||||
|
||||
@Override
|
||||
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
|
||||
switch (type) {
|
||||
case (byte) 128:
|
||||
return CryptographyResult.fromList((ArrayList<Object>) readValue(buffer));
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
|
||||
if (value instanceof CryptographyResult) {
|
||||
stream.write(128);
|
||||
writeValue(stream, ((CryptographyResult) value).toList());
|
||||
} else {
|
||||
super.writeValue(stream, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
public interface MoxplatformApi {
|
||||
/** Platform APIs */
|
||||
@NonNull
|
||||
String getPersistentDataPath();
|
||||
|
||||
@NonNull
|
||||
String getCacheDataPath();
|
||||
|
||||
void openBatteryOptimisationSettings();
|
||||
|
||||
@NonNull
|
||||
Boolean isIgnoringBatteryOptimizations();
|
||||
/** Contacts APIs */
|
||||
void recordSentMessage(@NonNull String name, @NonNull String jid, @Nullable String avatarPath, @NonNull FallbackIconType fallbackIcon);
|
||||
/** Cryptography APIs */
|
||||
void encryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result<CryptographyResult> result);
|
||||
|
||||
void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result<CryptographyResult> result);
|
||||
|
||||
void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Result<byte[]> result);
|
||||
/** Media APIs */
|
||||
@NonNull
|
||||
Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth);
|
||||
/** Picker */
|
||||
void pickFiles(@NonNull FilePickerType type, @NonNull Boolean pickMultiple, @NonNull Result<List<String>> result);
|
||||
|
||||
/** The codec used by MoxplatformApi. */
|
||||
static @NonNull MessageCodec<Object> getCodec() {
|
||||
return MoxplatformApiCodec.INSTANCE;
|
||||
}
|
||||
/**Sets up an instance of `MoxplatformApi` to handle messages through the `binaryMessenger`. */
|
||||
static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable MoxplatformApi api) {
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
try {
|
||||
String output = api.getPersistentDataPath();
|
||||
wrapped.add(0, output);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
try {
|
||||
String output = api.getCacheDataPath();
|
||||
wrapped.add(0, output);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
try {
|
||||
api.openBatteryOptimisationSettings();
|
||||
wrapped.add(0, null);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
try {
|
||||
Boolean output = api.isIgnoringBatteryOptimizations();
|
||||
wrapped.add(0, output);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
String nameArg = (String) args.get(0);
|
||||
String jidArg = (String) args.get(1);
|
||||
String avatarPathArg = (String) args.get(2);
|
||||
FallbackIconType fallbackIconArg = args.get(3) == null ? null : FallbackIconType.values()[(int) args.get(3)];
|
||||
try {
|
||||
api.recordSentMessage(nameArg, jidArg, avatarPathArg, fallbackIconArg);
|
||||
wrapped.add(0, null);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
String sourcePathArg = (String) args.get(0);
|
||||
String destPathArg = (String) args.get(1);
|
||||
byte[] keyArg = (byte[]) args.get(2);
|
||||
byte[] ivArg = (byte[]) args.get(3);
|
||||
CipherAlgorithm algorithmArg = args.get(4) == null ? null : CipherAlgorithm.values()[(int) args.get(4)];
|
||||
String hashSpecArg = (String) args.get(5);
|
||||
Result<CryptographyResult> resultCallback =
|
||||
new Result<CryptographyResult>() {
|
||||
public void success(CryptographyResult result) {
|
||||
wrapped.add(0, result);
|
||||
reply.reply(wrapped);
|
||||
}
|
||||
|
||||
public void error(Throwable error) {
|
||||
ArrayList<Object> wrappedError = wrapError(error);
|
||||
reply.reply(wrappedError);
|
||||
}
|
||||
};
|
||||
|
||||
api.encryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
String sourcePathArg = (String) args.get(0);
|
||||
String destPathArg = (String) args.get(1);
|
||||
byte[] keyArg = (byte[]) args.get(2);
|
||||
byte[] ivArg = (byte[]) args.get(3);
|
||||
CipherAlgorithm algorithmArg = args.get(4) == null ? null : CipherAlgorithm.values()[(int) args.get(4)];
|
||||
String hashSpecArg = (String) args.get(5);
|
||||
Result<CryptographyResult> resultCallback =
|
||||
new Result<CryptographyResult>() {
|
||||
public void success(CryptographyResult result) {
|
||||
wrapped.add(0, result);
|
||||
reply.reply(wrapped);
|
||||
}
|
||||
|
||||
public void error(Throwable error) {
|
||||
ArrayList<Object> wrappedError = wrapError(error);
|
||||
reply.reply(wrappedError);
|
||||
}
|
||||
};
|
||||
|
||||
api.decryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
String sourcePathArg = (String) args.get(0);
|
||||
String hashSpecArg = (String) args.get(1);
|
||||
Result<byte[]> resultCallback =
|
||||
new Result<byte[]>() {
|
||||
public void success(byte[] result) {
|
||||
wrapped.add(0, result);
|
||||
reply.reply(wrapped);
|
||||
}
|
||||
|
||||
public void error(Throwable error) {
|
||||
ArrayList<Object> wrappedError = wrapError(error);
|
||||
reply.reply(wrappedError);
|
||||
}
|
||||
};
|
||||
|
||||
api.hashFile(sourcePathArg, hashSpecArg, resultCallback);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
String srcArg = (String) args.get(0);
|
||||
String destArg = (String) args.get(1);
|
||||
Number maxWidthArg = (Number) args.get(2);
|
||||
try {
|
||||
Boolean output = api.generateVideoThumbnail(srcArg, destArg, (maxWidthArg == null) ? null : maxWidthArg.longValue());
|
||||
wrapped.add(0, output);
|
||||
}
|
||||
catch (Throwable exception) {
|
||||
ArrayList<Object> wrappedError = wrapError(exception);
|
||||
wrapped = wrappedError;
|
||||
}
|
||||
reply.reply(wrapped);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<Object> channel =
|
||||
new BasicMessageChannel<>(
|
||||
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.pickFiles", getCodec());
|
||||
if (api != null) {
|
||||
channel.setMessageHandler(
|
||||
(message, reply) -> {
|
||||
ArrayList<Object> wrapped = new ArrayList<Object>();
|
||||
ArrayList<Object> args = (ArrayList<Object>) message;
|
||||
FilePickerType typeArg = args.get(0) == null ? null : FilePickerType.values()[(int) args.get(0)];
|
||||
Boolean pickMultipleArg = (Boolean) args.get(1);
|
||||
Result<List<String>> resultCallback =
|
||||
new Result<List<String>>() {
|
||||
public void success(List<String> result) {
|
||||
wrapped.add(0, result);
|
||||
reply.reply(wrapped);
|
||||
}
|
||||
|
||||
public void error(Throwable error) {
|
||||
ArrayList<Object> wrappedError = wrapError(error);
|
||||
reply.reply(wrappedError);
|
||||
}
|
||||
};
|
||||
|
||||
api.pickFiles(typeArg, pickMultipleArg, resultCallback);
|
||||
});
|
||||
} else {
|
||||
channel.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,7 @@
|
||||
package me.polynom.moxplatform_android;
|
||||
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.GROUP_KEY_FOREGROUND;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.GROUP_KEY_MESSAGES;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.GROUP_KEY_OTHER;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
@ -91,15 +85,29 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
|
||||
}
|
||||
|
||||
public static boolean isManuallyStopped(Context context) {
|
||||
return context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE).getBoolean(manuallyStoppedKey, false);
|
||||
return context.getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE).getBoolean(manuallyStoppedKey, false);
|
||||
}
|
||||
public void setManuallyStopped(Context context, boolean value) {
|
||||
context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE)
|
||||
context.getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(manuallyStoppedKey, value)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
CharSequence name = "Moxxy Background Service";
|
||||
String description = "Executing Moxxy in the background";
|
||||
|
||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
NotificationChannel channel = new NotificationChannel("FOREGROUND_DEFAULT", name, importance);
|
||||
channel.setDescription(description);
|
||||
|
||||
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateNotificationInfo() {
|
||||
String packageName = getApplicationContext().getPackageName();
|
||||
Intent i = getPackageManager().getLaunchIntentForPackage(packageName);
|
||||
@ -114,7 +122,7 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
|
||||
| (PendingIntent.FLAG_MUTABLE & (aboveS ? PendingIntent.FLAG_MUTABLE : 0))
|
||||
);
|
||||
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, "foreground_service")
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, "FOREGROUND_DEFAULT")
|
||||
.setSmallIcon(R.drawable.ic_service_icon)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(true)
|
||||
@ -143,7 +151,7 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
|
||||
FlutterInjector.instance().flutterLoader().startInitialization(getApplicationContext());
|
||||
}
|
||||
|
||||
long entrypointHandle = getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE)
|
||||
long entrypointHandle = getSharedPreferences(MoxplatformAndroidPlugin.sharedPrefKey, MODE_PRIVATE)
|
||||
.getLong(MoxplatformAndroidPlugin.entrypointKey, 0);
|
||||
FlutterInjector.instance().flutterLoader().ensureInitializationComplete(getApplicationContext(), null);
|
||||
FlutterCallbackInformation callback = FlutterCallbackInformation.lookupCallbackInformation(entrypointHandle);
|
||||
@ -229,6 +237,7 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
createNotificationChannel();
|
||||
notificationBody = "Preparing...";
|
||||
updateNotificationInfo();
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (MoxplatformAndroidPlugin.getStartAtBoot(context)) {
|
||||
if (BackgroundService.wakeLock == null) {
|
||||
Log.d(TAG, "Wakelock is null. Acquiring it...");
|
||||
BackgroundService.getLock(context).acquire(MoxplatformConstants.WAKE_LOCK_DURATION);
|
||||
Log.d(TAG, "Wakelock acquired...");
|
||||
}
|
||||
if (BackgroundService.wakeLock == null) {
|
||||
Log.d(TAG, "Wakelock is null. Acquiring it...");
|
||||
BackgroundService.getLock(context).acquire(MoxplatformConstants.WAKE_LOCK_DURATION);
|
||||
Log.d(TAG, "Wakelock acquired...");
|
||||
}
|
||||
|
||||
ContextCompat.startForegroundService(context, new Intent(context, BackgroundService.class));
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
package me.polynom.moxplatform_android
|
||||
|
||||
// The tag we use for logging.
|
||||
const val TAG = "Moxplatform"
|
||||
|
||||
// The size of the buffer to hashing, encryption, and decryption in bytes.
|
||||
const val BUFFER_SIZE = 8096
|
||||
|
||||
const val GROUP_KEY_FOREGROUND = "foreground"
|
||||
const val GROUP_KEY_MESSAGES = "messages"
|
||||
const val GROUP_KEY_OTHER = "other"
|
||||
|
||||
// The data key for text entered in the notification's reply field
|
||||
const val REPLY_TEXT_KEY = "key_reply_text"
|
||||
|
||||
// The key for the notification id to mark as read
|
||||
const val MARK_AS_READ_ID_KEY = "notification_id"
|
||||
|
||||
// Values for actions performed through the notification
|
||||
const val REPLY_ACTION = "reply"
|
||||
const val MARK_AS_READ_ACTION = "mark_as_read"
|
||||
const val TAP_ACTION = "tap"
|
||||
|
||||
// Extra data keys for the intents that reach the NotificationReceiver
|
||||
const val NOTIFICATION_EXTRA_JID_KEY = "jid"
|
||||
const val NOTIFICATION_EXTRA_ID_KEY = "notification_id"
|
||||
|
||||
// Extra data keys for messages embedded inside the notification style
|
||||
const val NOTIFICATION_MESSAGE_EXTRA_MIME = "mime"
|
||||
const val NOTIFICATION_MESSAGE_EXTRA_PATH = "path"
|
||||
|
||||
const val MOXPLATFORM_FILEPROVIDER_ID = "me.polynom.moxplatform_android.fileprovider"
|
||||
|
||||
// Shared preferences keys
|
||||
const val SHARED_PREFERENCES_KEY = "me.polynom.moxplatform_android"
|
||||
const val SHARED_PREFERENCES_YOU_KEY = "you"
|
||||
const val SHARED_PREFERENCES_MARK_AS_READ_KEY = "mark_as_read"
|
||||
const val SHARED_PREFERENCES_REPLY_KEY = "reply"
|
||||
const val SHARED_PREFERENCES_AVATAR_KEY = "avatar_path"
|
||||
|
||||
// TODO: Maybe try again to rewrite the entire plugin in Kotlin
|
||||
//const val METHOD_CHANNEL_KEY = "me.polynom.moxplatform_android"
|
||||
//const val BACKGROUND_METHOD_CHANNEL_KEY = METHOD_CHANNEL_KEY + "_bg"
|
||||
|
||||
// https://github.com/ekasetiawans/flutter_background_service/blob/e427f3b70138ec26f9671c2617f9061f25eade6f/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BootReceiver.java#L20
|
||||
//const val WAKELOCK_DURATION = 10*60*1000L;
|
||||
|
||||
// The name of the wakelock the background service manager holds.
|
||||
//const val SERVICE_WAKELOCK_NAME = "BackgroundService.Lock"
|
||||
|
||||
//const val DATA_RECEIVER_METHOD_NAME = "dataReceived"
|
||||
|
||||
// Shared preferences keys
|
||||
//const val SHARED_PREFERENCES_KEY = "me.polynom.moxplatform_android"
|
||||
//const val SP_MANUALLY_STOPPED_KEY = "manually_stopped"
|
||||
//const val SP_ENTRYPOINT_KEY = "entrypoint_handle"
|
||||
//const val SP_EXTRA_DATA_KEY = "extra_data"
|
||||
//const val SP_AUTO_START_AT_BOOT_KEY = "auto_start_at_boot"
|
@ -1,158 +0,0 @@
|
||||
package me.polynom.moxplatform_android
|
||||
|
||||
import android.util.Log
|
||||
import me.polynom.moxplatform_android.Api.CipherAlgorithm
|
||||
import me.polynom.moxplatform_android.Api.CryptographyResult
|
||||
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.Exception
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
// A FileOutputStream that continuously hashes whatever it writes to the file.
|
||||
private class HashedFileOutputStream(name: String, hashAlgorithm: String) : FileOutputStream(name) {
|
||||
private val digest: MessageDigest
|
||||
|
||||
init {
|
||||
this.digest = MessageDigest.getInstance(hashAlgorithm)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteArray, offset: Int, length: Int) {
|
||||
super.write(buffer, offset, length)
|
||||
|
||||
digest.update(buffer, offset, length)
|
||||
}
|
||||
|
||||
fun digest() : ByteArray {
|
||||
return digest.digest()
|
||||
}
|
||||
}
|
||||
|
||||
fun getCipherSpecFromInteger(algorithm: CipherAlgorithm): String {
|
||||
return when (algorithm) {
|
||||
CipherAlgorithm.AES128GCM_NO_PADDING -> "AES_128/GCM/NoPadding"
|
||||
CipherAlgorithm.AES256GCM_NO_PADDING -> "AES_256/GCM/NoPadding"
|
||||
CipherAlgorithm.AES256CBC_PKCS7 -> "AES_256/CBC/PKCS7PADDING"
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the hash, specified by @algorithm, of the file at path @srcFile. If an exception
|
||||
// occurs, returns null. If everything went well, returns the raw hash of @srcFile.
|
||||
fun hashFile(srcFile: String, algorithm: String, result: Api.Result<ByteArray?>) {
|
||||
thread(start = true) {
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
try {
|
||||
val digest = MessageDigest.getInstance(algorithm)
|
||||
val fInputStream = FileInputStream(srcFile)
|
||||
var length: Int
|
||||
|
||||
while (true) {
|
||||
length = fInputStream.read()
|
||||
if (length <= 0) break
|
||||
|
||||
// Only update the digest if we read more than 0 bytes
|
||||
digest.update(buffer, 0, length)
|
||||
}
|
||||
|
||||
fInputStream.close()
|
||||
|
||||
result.success(digest.digest())
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "[hashFile]: " + e.stackTraceToString())
|
||||
result.success(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the plaintext file at @src to @dest using the secret key @key and the IV @iv. The algorithm is chosen using @cipherAlgorithm. The file is additionally
|
||||
// hashed before and after encryption using the hash algorithm specified by @hashAlgorithm.
|
||||
fun encryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result<CryptographyResult?>) {
|
||||
thread(start = true) {
|
||||
val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm)
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
val secretKey = SecretKeySpec(key, cipherSpec)
|
||||
try {
|
||||
val digest = MessageDigest.getInstance(hashAlgorithm)
|
||||
val cipher = Cipher.getInstance(cipherSpec)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||
|
||||
val fileInputStream = FileInputStream(src)
|
||||
val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm)
|
||||
val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher)
|
||||
|
||||
var length: Int
|
||||
while (true) {
|
||||
length = fileInputStream.read(buffer)
|
||||
if (length <= 0) break
|
||||
|
||||
digest.update(buffer, 0, length)
|
||||
cipherOutputStream.write(buffer, 0, length)
|
||||
}
|
||||
|
||||
// Flush and close
|
||||
cipherOutputStream.flush()
|
||||
cipherOutputStream.close()
|
||||
fileInputStream.close()
|
||||
|
||||
result.success(
|
||||
CryptographyResult().apply {
|
||||
plaintextHash = digest.digest()
|
||||
ciphertextHash = fileOutputStream.digest()
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "[encryptAndHash]: " + e.stackTraceToString())
|
||||
result.success(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt the ciphertext file at @src to @dest using the secret key @key and the IV @iv. The algorithm is chosen using @cipherAlgorithm. The file is additionally
|
||||
// hashed before and after decryption using the hash algorithm specified by @hashAlgorithm.
|
||||
fun decryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result<CryptographyResult?>) {
|
||||
thread(start = true) {
|
||||
val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm)
|
||||
// Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
val secretKey = SecretKeySpec(key, cipherSpec)
|
||||
try {
|
||||
val digest = MessageDigest.getInstance(hashAlgorithm)
|
||||
val cipher = Cipher.getInstance(cipherSpec)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||
|
||||
val fileInputStream = FileInputStream(src)
|
||||
val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm)
|
||||
val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher)
|
||||
|
||||
// Read, decrypt, and hash until we read 0 bytes
|
||||
var length: Int
|
||||
while (true) {
|
||||
length = fileInputStream.read(buffer)
|
||||
if (length <= 0) break
|
||||
|
||||
digest.update(buffer, 0, length)
|
||||
cipherOutputStream.write(buffer, 0, length)
|
||||
}
|
||||
|
||||
// Flush
|
||||
cipherOutputStream.flush()
|
||||
cipherOutputStream.close()
|
||||
fileInputStream.close()
|
||||
|
||||
result.success(
|
||||
CryptographyResult().apply {
|
||||
plaintextHash = digest.digest()
|
||||
ciphertextHash = fileOutputStream.digest()
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "[hashAndDecrypt]: " + e.stackTraceToString())
|
||||
result.success(null)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package me.polynom.moxplatform_android;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class HashedFileOutputStream extends FileOutputStream {
|
||||
public MessageDigest digest;
|
||||
|
||||
public HashedFileOutputStream(String name, String hashSpec) throws FileNotFoundException, NoSuchAlgorithmException {
|
||||
super(name);
|
||||
|
||||
digest = MessageDigest.getInstance(hashSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
super.write(b, off, len);
|
||||
|
||||
digest.update(b, off, len);
|
||||
}
|
||||
|
||||
public String getHexHash() {
|
||||
StringBuffer result = new StringBuffer();
|
||||
for (byte b : digest.digest()) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public byte[] getHash() {
|
||||
return digest.digest();
|
||||
}
|
||||
}
|
@ -1,318 +1,348 @@
|
||||
package me.polynom.moxplatform_android;
|
||||
|
||||
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
|
||||
import static androidx.core.content.ContextCompat.getSystemService;
|
||||
import static androidx.core.content.ContextCompat.startActivity;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.MOXPLATFORM_FILEPROVIDER_ID;
|
||||
import static me.polynom.moxplatform_android.ConstantsKt.SHARED_PREFERENCES_KEY;
|
||||
import static me.polynom.moxplatform_android.CryptoKt.*;
|
||||
import static me.polynom.moxplatform_android.PickerKt.filePickerRequest;
|
||||
import static me.polynom.moxplatform_android.PickerKt.onActivityResultImpl;
|
||||
import static me.polynom.moxplatform_android.RecordSentMessageKt.*;
|
||||
import static me.polynom.moxplatform_android.ThumbnailsKt.generateVideoThumbnailImplementation;
|
||||
|
||||
import me.polynom.moxplatform_android.Api.*;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.activity.result.PickVisualMediaRequest;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.embedding.engine.plugins.service.ServiceAware;
|
||||
import io.flutter.embedding.engine.plugins.service.ServicePluginBinding;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import io.flutter.plugin.common.EventChannel.EventSink;
|
||||
import io.flutter.plugin.common.EventChannel.StreamHandler;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
import io.flutter.plugin.common.PluginRegistry.Registrar;
|
||||
import io.flutter.plugin.common.PluginRegistry;
|
||||
import io.flutter.plugin.common.JSONMethodCodec;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware, ActivityAware, PluginRegistry.ActivityResultListener, MoxplatformApi {
|
||||
public static final String entrypointKey = "entrypoint_handle";
|
||||
public static final String extraDataKey = "extra_data";
|
||||
private static final String autoStartAtBootKey = "auto_start_at_boot";
|
||||
private static final String TAG = "moxplatform_android";
|
||||
public static final String methodChannelKey = "me.polynom.moxplatform_android";
|
||||
public static final String dataReceivedMethodName = "dataReceived";
|
||||
public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, ServiceAware {
|
||||
public static final String entrypointKey = "entrypoint_handle";
|
||||
public static final String extraDataKey = "extra_data";
|
||||
private static final String autoStartAtBootKey = "auto_start_at_boot";
|
||||
public static final String sharedPrefKey = "me.polynom.moxplatform_android";
|
||||
private static final String TAG = "moxplatform_android";
|
||||
public static final String methodChannelKey = "me.polynom.moxplatform_android";
|
||||
public static final String dataReceivedMethodName = "dataReceived";
|
||||
|
||||
private static final List<MoxplatformAndroidPlugin> _instances = new ArrayList<>();
|
||||
private BackgroundService service;
|
||||
private MethodChannel channel;
|
||||
private static final List<MoxplatformAndroidPlugin> _instances = new ArrayList<>();
|
||||
private BackgroundService service;
|
||||
private MethodChannel channel;
|
||||
private Context context;
|
||||
|
||||
public static Activity activity;
|
||||
private Context context;
|
||||
public MoxplatformAndroidPlugin() {
|
||||
_instances.add(this);
|
||||
}
|
||||
|
||||
public MoxplatformAndroidPlugin() {
|
||||
_instances.add(this);
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
|
||||
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey);
|
||||
channel.setMethodCallHandler(this);
|
||||
context = flutterPluginBinding.getApplicationContext();
|
||||
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
|
||||
localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey));
|
||||
|
||||
Log.d(TAG, "Attached to engine");
|
||||
}
|
||||
|
||||
static void registerWith(Registrar registrar) {
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context());
|
||||
final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin();
|
||||
localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey));
|
||||
|
||||
final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE);
|
||||
channel.setMethodCallHandler(plugin);
|
||||
plugin.channel = channel;
|
||||
|
||||
Log.d(TAG, "Registered against registrar");
|
||||
}
|
||||
|
||||
/// Store the entrypoint handle and extra data for the background service.
|
||||
private void configure(long entrypointHandle, String extraData) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE);
|
||||
prefs.edit()
|
||||
.putLong(entrypointKey, entrypointHandle)
|
||||
.putString(extraDataKey, extraData)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static long getHandle(Context c) {
|
||||
return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getLong(entrypointKey, 0);
|
||||
}
|
||||
|
||||
public static String getExtraData(Context c) {
|
||||
return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getString(extraDataKey, "");
|
||||
}
|
||||
|
||||
public static void setStartAtBoot(Context c, boolean value) {
|
||||
c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(autoStartAtBootKey, value)
|
||||
.apply();
|
||||
}
|
||||
public static boolean getStartAtBoot(Context c) {
|
||||
return c.getSharedPreferences(sharedPrefKey, Context.MODE_PRIVATE).getBoolean(autoStartAtBootKey, false);
|
||||
}
|
||||
|
||||
private boolean isRunning() {
|
||||
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (BackgroundService.class.getName().equals(info.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
return onActivityResultImpl(context, requestCode, resultCode, data);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
|
||||
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), methodChannelKey);
|
||||
channel.setMethodCallHandler(this);
|
||||
context = flutterPluginBinding.getApplicationContext();
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
switch (call.method) {
|
||||
case "configure":
|
||||
ArrayList args = (ArrayList) call.arguments;
|
||||
long handle = (long) args.get(0);
|
||||
String extraData = (String) args.get(1);
|
||||
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
|
||||
localBroadcastManager.registerReceiver(this, new IntentFilter(methodChannelKey));
|
||||
|
||||
MoxplatformApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
|
||||
|
||||
Log.d(TAG, "Attached to engine");
|
||||
}
|
||||
|
||||
static void registerWith(Registrar registrar) {
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(registrar.context());
|
||||
final MoxplatformAndroidPlugin plugin = new MoxplatformAndroidPlugin();
|
||||
localBroadcastManager.registerReceiver(plugin, new IntentFilter(methodChannelKey));
|
||||
activity = registrar.activity();
|
||||
|
||||
final MethodChannel channel = new MethodChannel(registrar.messenger(), "me.polynom/background_service_android", JSONMethodCodec.INSTANCE);
|
||||
channel.setMethodCallHandler(plugin);
|
||||
plugin.channel = channel;
|
||||
|
||||
Log.d(TAG, "Registered against registrar");
|
||||
}
|
||||
|
||||
/// Store the entrypoint handle and extra data for the background service.
|
||||
private void configure(long entrypointHandle, String extraData) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
|
||||
prefs.edit().putLong(entrypointKey, entrypointHandle).putString(extraDataKey, extraData).apply();
|
||||
}
|
||||
|
||||
public static long getHandle(Context c) {
|
||||
return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getLong(entrypointKey, 0);
|
||||
}
|
||||
|
||||
public static String getExtraData(Context c) {
|
||||
return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getString(extraDataKey, "");
|
||||
}
|
||||
|
||||
public static void setStartAtBoot(Context c, boolean value) {
|
||||
c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).edit().putBoolean(autoStartAtBootKey, value).apply();
|
||||
}
|
||||
|
||||
public static boolean getStartAtBoot(Context c) {
|
||||
return c.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE).getBoolean(autoStartAtBootKey, false);
|
||||
}
|
||||
|
||||
private boolean isRunning() {
|
||||
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (BackgroundService.class.getName().equals(info.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
configure(handle, extraData);
|
||||
result.success(true);
|
||||
break;
|
||||
case "isRunning":
|
||||
result.success(isRunning());
|
||||
break;
|
||||
case "start":
|
||||
MoxplatformAndroidPlugin.setStartAtBoot(context, true);
|
||||
BackgroundService.enqueue(context);
|
||||
Intent intent = new Intent(context, BackgroundService.class);
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
Log.d(TAG, "Service started");
|
||||
result.success(true);
|
||||
break;
|
||||
case "sendData":
|
||||
for (MoxplatformAndroidPlugin plugin : _instances) {
|
||||
if (plugin.service != null) {
|
||||
plugin.service.receiveData((String) call.arguments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
case "encryptFile":
|
||||
Thread encryptionThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayList args = (ArrayList) call.arguments;
|
||||
String src = (String) args.get(0);
|
||||
String dest = (String) args.get(1);
|
||||
byte[] key = (byte[]) args.get(2);
|
||||
byte[] iv = (byte[]) args.get(3);
|
||||
int algorithm = (int) args.get(4);
|
||||
String hashSpec = (String) args.get(5);
|
||||
|
||||
return false;
|
||||
result.success(encryptFile(src, dest, key, iv, algorithm, hashSpec));
|
||||
}
|
||||
});
|
||||
encryptionThread.start();
|
||||
break;
|
||||
case "decryptFile":
|
||||
Thread decryptionThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayList args = (ArrayList) call.arguments;
|
||||
String src = (String) args.get(0);
|
||||
String dest = (String) args.get(1);
|
||||
byte[] key = (byte[]) args.get(2);
|
||||
byte[] iv = (byte[]) args.get(3);
|
||||
int algorithm = (int) args.get(4);
|
||||
String hashSpec = (String) args.get(5);
|
||||
|
||||
result.success(decryptFile(src, dest, key, iv, algorithm, hashSpec));
|
||||
}
|
||||
});
|
||||
decryptionThread.start();
|
||||
break;
|
||||
case "hashFile":
|
||||
Thread hashingThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayList args = (ArrayList) call.arguments;
|
||||
String src = (String) args.get(0);
|
||||
String hashSpec = (String) args.get(1);
|
||||
|
||||
result.success(hashFile(src, hashSpec));
|
||||
}
|
||||
});
|
||||
hashingThread.start();
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction() == null) return;
|
||||
|
||||
if (intent.getAction().equalsIgnoreCase(methodChannelKey)) {
|
||||
String data = intent.getStringExtra("data");
|
||||
|
||||
if (channel != null) {
|
||||
channel.invokeMethod(dataReceivedMethodName, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel.setMethodCallHandler(null);
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
|
||||
Log.d(TAG, "Detached from engine");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToService(@NonNull ServicePluginBinding binding) {
|
||||
Log.d(TAG, "Attached to service");
|
||||
this.service = (BackgroundService) binding.getService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromService() {
|
||||
Log.d(TAG, "Detached from service");
|
||||
this.service = null;
|
||||
}
|
||||
|
||||
private String getCipherSpecFromInteger(int algorithm) {
|
||||
switch (algorithm) {
|
||||
case 0: return "AES_128/GCM/NoPadding";
|
||||
case 1: return "AES_256/GCM/NoPadding";
|
||||
case 2: return "AES_256/CBC/PKCS7PADDING";
|
||||
default:
|
||||
Log.d(TAG, "INVALID ALGORITHM");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, byte[]> encryptFile(String src, String dest, byte[] key, byte[] iv, int algorithm, String hashSpec) {
|
||||
String spec = getCipherSpecFromInteger(algorithm);
|
||||
if (spec.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull io.flutter.plugin.common.MethodChannel.Result result) {
|
||||
switch (call.method) {
|
||||
case "configure":
|
||||
ArrayList args = (ArrayList) call.arguments;
|
||||
long handle = (long) args.get(0);
|
||||
String extraData = (String) args.get(1);
|
||||
|
||||
configure(handle, extraData);
|
||||
result.success(true);
|
||||
break;
|
||||
case "isRunning":
|
||||
result.success(isRunning());
|
||||
break;
|
||||
case "start":
|
||||
MoxplatformAndroidPlugin.setStartAtBoot(context, true);
|
||||
BackgroundService.enqueue(context);
|
||||
Intent intent = new Intent(context, BackgroundService.class);
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
Log.d(TAG, "Service started");
|
||||
result.success(true);
|
||||
break;
|
||||
case "sendData":
|
||||
for (MoxplatformAndroidPlugin plugin : _instances) {
|
||||
if (plugin.service != null) {
|
||||
plugin.service.receiveData((String) call.arguments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
// Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
|
||||
byte[] buffer = new byte[8096];
|
||||
SecretKeySpec sk = new SecretKeySpec(key, spec);
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(hashSpec);
|
||||
Cipher cipher = Cipher.getInstance(spec);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sk, new IvParameterSpec(iv));
|
||||
FileInputStream fin = new FileInputStream(src);
|
||||
HashedFileOutputStream fout = new HashedFileOutputStream(dest, hashSpec);
|
||||
CipherOutputStream cout = new CipherOutputStream(fout, cipher);
|
||||
int len = 0;
|
||||
int bufLen = 0;
|
||||
while (true) {
|
||||
len = fin.read(buffer);
|
||||
if (len != 0 && len > 0) {
|
||||
md.update(buffer, 0, len);
|
||||
cout.write(buffer, 0, len);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cout.flush();
|
||||
cout.close();
|
||||
fin.close();
|
||||
|
||||
return new HashMap<String, byte[]>() {{
|
||||
put("plaintext_hash", md.digest());
|
||||
put("ciphertext_hash", fout.getHash());
|
||||
}};
|
||||
} catch (Exception ex) {
|
||||
Log.d(TAG, "ENC: " + ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, byte[]> decryptFile(String src, String dest, byte[] key, byte[] iv, int algorithm, String hashSpec) {
|
||||
String spec = getCipherSpecFromInteger(algorithm);
|
||||
if (spec.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction() == null) return;
|
||||
|
||||
if (intent.getAction().equalsIgnoreCase(methodChannelKey)) {
|
||||
String data = intent.getStringExtra("data");
|
||||
|
||||
if (channel != null) {
|
||||
channel.invokeMethod(dataReceivedMethodName, data);
|
||||
}
|
||||
// Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
|
||||
byte[] buffer = new byte[8096];
|
||||
SecretKeySpec sk = new SecretKeySpec(key, spec);
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(spec);
|
||||
cipher.init(Cipher.DECRYPT_MODE, sk, new IvParameterSpec(iv));
|
||||
FileInputStream fin = new FileInputStream(src);
|
||||
HashedFileOutputStream fout = new HashedFileOutputStream(dest, hashSpec);
|
||||
CipherOutputStream cout = new CipherOutputStream(fout, cipher);
|
||||
MessageDigest md = MessageDigest.getInstance(hashSpec);
|
||||
Log.d(TAG, "Reading from " + src + ", writing to " + dest);
|
||||
int len = 0;
|
||||
while (true) {
|
||||
len = fin.read(buffer);
|
||||
if (len != 0 && len > 0) {
|
||||
cout.write(buffer, 0, len);
|
||||
md.update(buffer, 0, len);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cout.flush();
|
||||
cout.close();
|
||||
fin.close();
|
||||
|
||||
return new HashMap<String, byte[]>() {{
|
||||
put("plaintext_hash", md.digest());
|
||||
put("ciphertext_hash", fout.getHash());
|
||||
}};
|
||||
} catch (Exception ex) {
|
||||
Log.d(TAG, "DEC: " + ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel.setMethodCallHandler(null);
|
||||
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.context);
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
public byte[] hashFile(String src, String algorithm) {
|
||||
byte[] buffer = new byte[8096];
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||
FileInputStream fin = new FileInputStream(src);
|
||||
int len = 0;
|
||||
while (true) {
|
||||
len = fin.read(buffer);
|
||||
if (len != 0 && len > 0) {
|
||||
md.update(buffer, 0, len);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Detached from engine");
|
||||
return md.digest();
|
||||
} catch (Exception ex) {
|
||||
Log.d(TAG, "Hash: " + ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToService(@NonNull ServicePluginBinding binding) {
|
||||
Log.d(TAG, "Attached to service");
|
||||
this.service = (BackgroundService) binding.getService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromService() {
|
||||
Log.d(TAG, "Detached from service");
|
||||
this.service = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getPersistentDataPath() {
|
||||
return context.getFilesDir().getPath();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getCacheDataPath() {
|
||||
return context.getCacheDir().getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openBatteryOptimisationSettings() {
|
||||
final Uri packageUri = Uri.parse("package:" + context.getPackageName());
|
||||
Log.d(TAG, packageUri.toString());
|
||||
final Intent intent = new Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, packageUri);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Boolean isIgnoringBatteryOptimizations() {
|
||||
final PowerManager pm = context.getSystemService(PowerManager.class);
|
||||
return pm.isIgnoringBatteryOptimizations(context.getPackageName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordSentMessage(@NonNull String name, @NonNull String jid, @Nullable String avatarPath, @NonNull FallbackIconType fallbackIcon) {
|
||||
systemRecordSentMessage(context, name, jid, avatarPath, fallbackIcon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Api.Result<CryptographyResult> result) {
|
||||
CryptoKt.encryptAndHash(
|
||||
sourcePath,
|
||||
destPath,
|
||||
key,
|
||||
iv,
|
||||
algorithm,
|
||||
hashSpec,
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Api.Result<CryptographyResult> result) {
|
||||
CryptoKt.decryptAndHash(
|
||||
sourcePath,
|
||||
destPath,
|
||||
key,
|
||||
iv,
|
||||
algorithm,
|
||||
hashSpec,
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Api.Result<byte[]> result) {
|
||||
CryptoKt.hashFile(sourcePath, hashSpec, result);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Boolean generateVideoThumbnail(@NonNull String src, @NonNull String dest, @NonNull Long maxWidth) {
|
||||
return generateVideoThumbnailImplementation(src, dest, maxWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pickFiles(@NonNull FilePickerType type, @NonNull Boolean pickMultiple, @NonNull Api.Result<List<String>> result) {
|
||||
filePickerRequest(context, activity, type, pickMultiple, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToActivity(ActivityPluginBinding binding) {
|
||||
activity = binding.getActivity();
|
||||
binding.addActivityResultListener(this);
|
||||
Log.d(TAG, "Activity attached");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivity() {}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivityForConfigChanges() {}
|
||||
|
||||
@Override
|
||||
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {}
|
||||
}
|
||||
}
|
||||
|
@ -1,226 +0,0 @@
|
||||
package me.polynom.moxplatform_android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
object RequestTracker {
|
||||
val requests: MutableMap<Int, Api.Result<Any>> = mutableMapOf()
|
||||
}
|
||||
|
||||
const val PICK_FILE_REQUEST = 41;
|
||||
const val PICK_FILES_REQUEST = 42;
|
||||
|
||||
fun genericFilePickerRequest(activity: Activity?, pickMultiple: Boolean, result: Api.Result<List<String>>) {
|
||||
val pickIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, pickMultiple);
|
||||
}
|
||||
|
||||
RequestTracker.requests[PICK_FILE_REQUEST] = result as Api.Result<Any>;
|
||||
activity?.startActivityForResult(pickIntent, PICK_FILE_REQUEST)
|
||||
}
|
||||
|
||||
fun filePickerRequest(
|
||||
context: Context,
|
||||
activity: Activity?,
|
||||
type: Api.FilePickerType,
|
||||
pickMultiple: Boolean,
|
||||
result: Api.Result<List<String>>
|
||||
) {
|
||||
if (type == Api.FilePickerType.GENERIC) {
|
||||
return genericFilePickerRequest(activity, pickMultiple, result)
|
||||
}
|
||||
|
||||
val pickerType = when (type) {
|
||||
Api.FilePickerType.IMAGE -> ActivityResultContracts.PickVisualMedia.ImageOnly
|
||||
Api.FilePickerType.VIDEO -> ActivityResultContracts.PickVisualMedia.VideoOnly
|
||||
Api.FilePickerType.IMAGE_AND_VIDEO -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||
// TODO
|
||||
Api.FilePickerType.GENERIC -> ActivityResultContracts.PickVisualMedia.ImageAndVideo
|
||||
}
|
||||
|
||||
val pick = when (pickMultiple) {
|
||||
false -> ActivityResultContracts.PickVisualMedia()
|
||||
true -> ActivityResultContracts.PickMultipleVisualMedia()
|
||||
}
|
||||
|
||||
val requestCode = if (pickMultiple) PICK_FILES_REQUEST else PICK_FILE_REQUEST
|
||||
val pickIntent = pick.createIntent(context, PickVisualMediaRequest(pickerType))
|
||||
RequestTracker.requests[requestCode] = result as Api.Result<Any>
|
||||
Log.d(TAG, "Tracked size ${RequestTracker.requests.size}")
|
||||
|
||||
if (activity == null) {
|
||||
Log.w(TAG, "Activity is null")
|
||||
}
|
||||
activity?.startActivityForResult(pickIntent, requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the file from the given content URI to a temporary directory, retaining the original
|
||||
* file name if possible.
|
||||
*
|
||||
*
|
||||
* Each file is placed in its own directory to avoid conflicts according to the following
|
||||
* scheme: {cacheDir}/{randomUuid}/{fileName}
|
||||
*
|
||||
*
|
||||
* File extension is changed to match MIME type of the file, if known. Otherwise, the extension
|
||||
* is left unchanged.
|
||||
*
|
||||
*
|
||||
* If the original file name is unknown, a predefined "image_picker" filename is used and the
|
||||
* file extension is deduced from the mime type (with fallback to ".jpg" in case of failure).
|
||||
*/
|
||||
fun getPathFromUri(context: Context, uri: Uri): String? {
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri).use { inputStream ->
|
||||
val uuid = UUID.randomUUID().toString()
|
||||
val targetDirectory = File(context.cacheDir, uuid)
|
||||
targetDirectory.mkdir()
|
||||
// TODO(SynSzakala) according to the docs, `deleteOnExit` does not work reliably on Android; we should preferably
|
||||
// just clear the picked files after the app startup.
|
||||
targetDirectory.deleteOnExit()
|
||||
var fileName = getImageName(context, uri)
|
||||
var extension = getImageExtension(context, uri)
|
||||
if (fileName == null) {
|
||||
Log.w("FileUtils", "Cannot get file name for $uri")
|
||||
if (extension == null) extension = ".jpg"
|
||||
fileName = "image_picker$extension"
|
||||
} else if (extension != null) {
|
||||
fileName = getBaseName(fileName) + extension
|
||||
}
|
||||
val file = File(targetDirectory, fileName)
|
||||
FileOutputStream(file).use { outputStream ->
|
||||
copy(inputStream!!, outputStream)
|
||||
return file.path
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// If closing the output stream fails, we cannot be sure that the
|
||||
// target file was written in full. Flushing the stream merely moves
|
||||
// the bytes into the OS, not necessarily to the file.
|
||||
return null
|
||||
} catch (e: SecurityException) {
|
||||
// Calling `ContentResolver#openInputStream()` has been reported to throw a
|
||||
// `SecurityException` on some devices in certain circumstances. Instead of crashing, we
|
||||
// return `null`.
|
||||
//
|
||||
// See https://github.com/flutter/flutter/issues/100025 for more details.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** @return extension of image with dot, or null if it's empty.
|
||||
*/
|
||||
private fun getImageExtension(context: Context, uriImage: Uri): String? {
|
||||
val extension: String?
|
||||
extension = try {
|
||||
if (uriImage.scheme == ContentResolver.SCHEME_CONTENT) {
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
mime.getExtensionFromMimeType(context.contentResolver.getType(uriImage))
|
||||
} else {
|
||||
MimeTypeMap.getFileExtensionFromUrl(
|
||||
Uri.fromFile(File(uriImage.path)).toString()
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
return if (extension == null || extension.isEmpty()) {
|
||||
null
|
||||
} else ".$extension"
|
||||
}
|
||||
|
||||
/** @return name of the image provided by ContentResolver; this may be null.
|
||||
*/
|
||||
private fun getImageName(context: Context, uriImage: Uri): String? {
|
||||
queryImageName(context, uriImage).use { cursor ->
|
||||
return if (cursor == null || !cursor.moveToFirst() || (cursor.columnCount < 1)) null else cursor.getString(
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryImageName(context: Context, uriImage: Uri): Cursor? {
|
||||
return context
|
||||
.contentResolver
|
||||
.query(uriImage, arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun copy(`in`: InputStream, out: OutputStream) {
|
||||
val buffer = ByteArray(4 * 1024)
|
||||
var bytesRead: Int
|
||||
while (`in`.read(buffer).also { bytesRead = it } != -1) {
|
||||
out.write(buffer, 0, bytesRead)
|
||||
}
|
||||
out.flush()
|
||||
}
|
||||
|
||||
private fun getBaseName(fileName: String): String {
|
||||
val lastDotIndex = fileName.lastIndexOf('.')
|
||||
return if (lastDotIndex < 0) {
|
||||
fileName
|
||||
} else fileName.substring(0, lastDotIndex)
|
||||
// Basename is everything before the last '.'.
|
||||
}
|
||||
|
||||
fun onActivityResultImpl(context: Context, requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
Log.d(TAG, "Got result for $requestCode with result $resultCode (${data?.action})")
|
||||
if (requestCode == PICK_FILE_REQUEST || requestCode == PICK_FILES_REQUEST) {
|
||||
Log.d(TAG, "Extra data ${data?.data}")
|
||||
val result = RequestTracker.requests.remove(requestCode);
|
||||
if (result == null) {
|
||||
Log.w(TAG, "Untracked response.")
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
// No files picked
|
||||
result!!.success(listOf<String>())
|
||||
return true;
|
||||
}
|
||||
|
||||
val pickedMultiple = requestCode == PICK_FILES_REQUEST
|
||||
val pickedFiles = mutableListOf<String>()
|
||||
if (pickedMultiple) {
|
||||
val intentUris = data!!.clipData
|
||||
if (data!!.clipData != null) {
|
||||
for (i in 0 until data!!.clipData!!.itemCount) {
|
||||
val path = getPathFromUri(context, data!!.clipData!!.getItemAt(i).uri)
|
||||
if (path != null) {
|
||||
pickedFiles.add(path )
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val path = getPathFromUri(context, data!!.data!!)
|
||||
if (path != null) {
|
||||
pickedFiles.add(path )
|
||||
}
|
||||
}
|
||||
|
||||
result!!.success(pickedFiles)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package me.polynom.moxplatform_android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import me.polynom.moxplatform_android.Api.FallbackIconType
|
||||
|
||||
/*
|
||||
* Uses Android's direct share API to create dynamic share targets that are compatible
|
||||
* with share_handler's media handling.
|
||||
* NOTE: The "system" prefix is to prevent confusion between pigeon's abstract recordSentMessage
|
||||
* method and this one.
|
||||
* */
|
||||
fun systemRecordSentMessage(context: Context, name: String, jid: String, avatarPath: String?, fallbackIcon: FallbackIconType) {
|
||||
val pkgName = context.packageName
|
||||
val intent = Intent(context, Class.forName("$pkgName.MainActivity")).apply {
|
||||
action = Intent.ACTION_SEND
|
||||
|
||||
// Compatibility with share_handler
|
||||
putExtra("conversationIdentifier", jid)
|
||||
}
|
||||
|
||||
val shortcutTarget = "$pkgName.dynamic_share_target"
|
||||
val shortcutBuilder = ShortcutInfoCompat.Builder(context, jid).apply {
|
||||
setShortLabel(name)
|
||||
setIsConversation()
|
||||
setCategories(setOf(shortcutTarget))
|
||||
setIntent(intent)
|
||||
setLongLived(true)
|
||||
}
|
||||
|
||||
val personBuilder = Person.Builder().apply {
|
||||
setKey(jid)
|
||||
setName(name)
|
||||
}
|
||||
|
||||
// Either set an avatar image OR a fallback icon
|
||||
if (avatarPath != null) {
|
||||
val icon = IconCompat.createWithAdaptiveBitmap(
|
||||
BitmapFactory.decodeFile(avatarPath),
|
||||
)
|
||||
shortcutBuilder.setIcon(icon)
|
||||
personBuilder.setIcon(icon)
|
||||
} else {
|
||||
val resourceId = when(fallbackIcon) {
|
||||
FallbackIconType.PERSON -> R.mipmap.person
|
||||
FallbackIconType.NOTES -> R.mipmap.notes
|
||||
// "Fallthrough"
|
||||
else -> R.mipmap.person
|
||||
}
|
||||
val icon = IconCompat.createWithResource(context, resourceId)
|
||||
shortcutBuilder.setIcon(icon)
|
||||
personBuilder.setIcon(icon)
|
||||
}
|
||||
|
||||
shortcutBuilder.setPerson(personBuilder.build())
|
||||
ShortcutManagerCompat.addDynamicShortcuts(
|
||||
context,
|
||||
listOf(shortcutBuilder.build()),
|
||||
)
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package me.polynom.moxplatform_android
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.util.Log
|
||||
import java.io.FileOutputStream
|
||||
|
||||
/*
|
||||
* Generate a video thumbnail using the first frame of the video at @src. Afterwards, scale it
|
||||
* down such that its width is equal to @maxWidth (while keeping the aspect ratio) and write it to
|
||||
* @dest.
|
||||
*
|
||||
* If everything went well, returns true. If we're unable to generate the thumbnail, returns false.
|
||||
* */
|
||||
fun generateVideoThumbnailImplementation(src: String, dest: String, maxWidth: Long): Boolean {
|
||||
try {
|
||||
val mmr = MediaMetadataRetriever().apply {
|
||||
setDataSource(src)
|
||||
}
|
||||
val unscaledThumbnail = mmr.getFrameAtTime(0) ?: return false
|
||||
|
||||
// Scale down the thumbnail while keeping the aspect ratio
|
||||
val scalingFactor = maxWidth.toDouble() / unscaledThumbnail.width;
|
||||
Log.d(TAG, "Scaling to $maxWidth from ${unscaledThumbnail.width} with scalingFactor $scalingFactor");
|
||||
val thumbnail = Bitmap.createScaledBitmap(
|
||||
unscaledThumbnail,
|
||||
(unscaledThumbnail.width * scalingFactor).toInt(),
|
||||
(unscaledThumbnail.height * scalingFactor).toInt(),
|
||||
false,
|
||||
)
|
||||
|
||||
// Write it to the destination file
|
||||
val fos = FileOutputStream(dest)
|
||||
thumbnail.compress(Bitmap.CompressFormat.JPEG, 75, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
return true;
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "Failed to create thumbnail for $src: ${ex.message}")
|
||||
ex.printStackTrace()
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
|
||||
</vector>
|
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17.34,20l-3.54,-3.54l1.41,-1.41l2.12,2.12l4.24,-4.24L23,14.34L17.34,20zM12,17c0,-3.87 3.13,-7 7,-7c1.08,0 2.09,0.25 3,0.68V4c0,-1.1 -0.9,-2 -2,-2H4C2.9,2 2,2.9 2,4v18l4,-4h6v0c0,-0.17 0.01,-0.33 0.03,-0.5C12.01,17.34 12,17.17 12,17z"/>
|
||||
</vector>
|
@ -1,14 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group android:scaleX="0.42988887"
|
||||
android:scaleY="0.42988887"
|
||||
android:translateX="6.8413334"
|
||||
android:translateY="6.8413334">
|
||||
<path
|
||||
android:pathData="M3,18h12v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h18v-2L3,11v2z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
@ -1,14 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group android:scaleX="0.483625"
|
||||
android:scaleY="0.483625"
|
||||
android:translateX="6.1965"
|
||||
android:translateY="6.1965">
|
||||
<path
|
||||
android:pathData="m12,12c2.21,0 4,-1.79 4,-4C16,5.79 14.21,4 12,4 9.79,4 8,5.79 8,8c0,2.21 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"
|
||||
android:fillColor="#fffff9"/>
|
||||
</group>
|
||||
</vector>
|
@ -1,5 +0,0 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#FFFFFF" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
|
||||
</vector>
|
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
|
||||
</vector>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/notes_background"/>
|
||||
<foreground android:drawable="@drawable/notes_foreground"/>
|
||||
</adaptive-icon>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/person_background"/>
|
||||
<foreground android:drawable="@drawable/person_foreground"/>
|
||||
</adaptive-icon>
|
Binary file not shown.
Before Width: | Height: | Size: 650 B |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="notes_background">#CF4AFF</color>
|
||||
</resources>
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="person_background">#CF4AFF</color>
|
||||
</resources>
|
@ -1,5 +1,6 @@
|
||||
library moxplatform_android;
|
||||
|
||||
export 'src/isolate_android.dart';
|
||||
export 'src/media_android.dart';
|
||||
export 'src/plugin_android.dart';
|
||||
export 'src/service_android.dart';
|
||||
|
@ -1,28 +0,0 @@
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
class AndroidContactsImplementation extends ContactsImplementation {
|
||||
final MoxplatformApi _api = MoxplatformApi();
|
||||
|
||||
@override
|
||||
Future<void> recordSentMessage(
|
||||
String name,
|
||||
String jid, {
|
||||
String? avatarPath,
|
||||
FallbackIconType fallbackIcon = FallbackIconType.none,
|
||||
}) async {
|
||||
// Ensure we always have an icon
|
||||
if (avatarPath != null) {
|
||||
assert(
|
||||
fallbackIcon != FallbackIconType.none,
|
||||
'If no avatar is specified, then a fallbackIcon must be set',
|
||||
);
|
||||
}
|
||||
|
||||
return _api.recordSentMessage(
|
||||
name,
|
||||
jid,
|
||||
avatarPath,
|
||||
fallbackIcon,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,49 +1,59 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
class AndroidCryptographyImplementation extends CryptographyImplementation {
|
||||
final MoxplatformApi _api = MoxplatformApi();
|
||||
final _methodChannel = const MethodChannel('me.polynom.moxplatform_android');
|
||||
|
||||
@override
|
||||
Future<CryptographyResult?> encryptFile(
|
||||
String sourcePath,
|
||||
String destPath,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
CipherAlgorithm algorithm,
|
||||
String hashSpec,
|
||||
) async {
|
||||
return _api.encryptFile(
|
||||
Future<CryptographyResult?> encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
|
||||
final dynamic resultRaw = await _methodChannel.invokeMethod<dynamic>('encryptFile', [
|
||||
sourcePath,
|
||||
destPath,
|
||||
key,
|
||||
iv,
|
||||
algorithm,
|
||||
algorithm.toInt(),
|
||||
hashSpec,
|
||||
]);
|
||||
if (resultRaw == null) return null;
|
||||
|
||||
// ignore: argument_type_not_assignable
|
||||
final result = Map<String, dynamic>.from(resultRaw);
|
||||
return CryptographyResult(
|
||||
result['plaintext_hash']! as Uint8List,
|
||||
result['ciphertext_hash']! as Uint8List,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CryptographyResult?> decryptFile(
|
||||
String sourcePath,
|
||||
String destPath,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
CipherAlgorithm algorithm,
|
||||
String hashSpec,
|
||||
) async {
|
||||
return _api.decryptFile(
|
||||
Future<CryptographyResult?> decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
|
||||
final dynamic resultRaw = await _methodChannel.invokeMethod<dynamic>('decryptFile', [
|
||||
sourcePath,
|
||||
destPath,
|
||||
key,
|
||||
iv,
|
||||
algorithm,
|
||||
algorithm.toInt(),
|
||||
hashSpec,
|
||||
]);
|
||||
if (resultRaw == null) return null;
|
||||
|
||||
// ignore: argument_type_not_assignable
|
||||
final result = Map<String, dynamic>.from(resultRaw);
|
||||
return CryptographyResult(
|
||||
result['plaintext_hash']! as Uint8List,
|
||||
result['ciphertext_hash']! as Uint8List,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async {
|
||||
return _api.hashFile(sourcePath, hashSpec);
|
||||
Future<Uint8List?> hashFile(String path, String hashSpec) async {
|
||||
final dynamic resultsRaw = await _methodChannel.invokeMethod<dynamic>('hashFile', [
|
||||
path,
|
||||
hashSpec,
|
||||
]);
|
||||
|
||||
if (resultsRaw == null) return null;
|
||||
|
||||
return resultsRaw as Uint8List;
|
||||
}
|
||||
}
|
||||
|
@ -4,18 +4,18 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxlib/awaitabledatasender.dart';
|
||||
import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:moxplatform_android/src/service_android.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
/// An [AwaitableDataSender] that uses flutter_background_service.
|
||||
class BackgroundServiceDataSender
|
||||
extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> {
|
||||
BackgroundServiceDataSender()
|
||||
: _channel = const MethodChannel('me.polynom.moxplatform_android'),
|
||||
super();
|
||||
final MethodChannel _channel;
|
||||
class BackgroundServiceDataSender extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> {
|
||||
|
||||
BackgroundServiceDataSender()
|
||||
: _channel = const MethodChannel('me.polynom.moxplatform_android'), super();
|
||||
final MethodChannel _channel;
|
||||
|
||||
@override
|
||||
Future<void> sendDataImpl(DataWrapper data) async {
|
||||
await _channel.invokeMethod<void>('sendData', jsonEncode(data.toJson()));
|
||||
@ -39,67 +39,56 @@ Future<void> androidEntrypoint() async {
|
||||
);
|
||||
final data = jsonDecode(result!) as Map<String, dynamic>;
|
||||
final entrypointHandle = data['genericEntrypoint']! as int;
|
||||
final entrypointCallbackHandle =
|
||||
CallbackHandle.fromRawHandle(entrypointHandle);
|
||||
final entrypoint =
|
||||
PluginUtilities.getCallbackFromHandle(entrypointCallbackHandle);
|
||||
final entrypointCallbackHandle = CallbackHandle.fromRawHandle(entrypointHandle);
|
||||
final entrypoint = PluginUtilities.getCallbackFromHandle(entrypointCallbackHandle);
|
||||
final handleUIEventHandle = data['eventHandle']! as int;
|
||||
final handleUIEventCallbackHandle =
|
||||
CallbackHandle.fromRawHandle(handleUIEventHandle);
|
||||
final handleUIEvent =
|
||||
PluginUtilities.getCallbackFromHandle(handleUIEventCallbackHandle);
|
||||
final handleUIEventCallbackHandle = CallbackHandle.fromRawHandle(handleUIEventHandle);
|
||||
final handleUIEvent = PluginUtilities.getCallbackFromHandle(handleUIEventCallbackHandle);
|
||||
|
||||
final srv = AndroidBackgroundService();
|
||||
GetIt.I.registerSingleton<BackgroundService>(srv);
|
||||
srv.init(
|
||||
entrypoint! as Future<void> Function(String),
|
||||
entrypoint! as Future<void> Function(),
|
||||
handleUIEvent! as Future<void> Function(Map<String, dynamic>? data),
|
||||
data['initialLocale']! as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// The Android specific implementation of the [IsolateHandler].
|
||||
class AndroidIsolateHandler extends IsolateHandler {
|
||||
|
||||
AndroidIsolateHandler()
|
||||
: _channel = const MethodChannel('me.polynom.moxplatform_android'),
|
||||
_dataSender = BackgroundServiceDataSender(),
|
||||
_log = Logger('AndroidIsolateHandler'),
|
||||
super();
|
||||
: _channel = const MethodChannel('me.polynom.moxplatform_android'),
|
||||
_dataSender = BackgroundServiceDataSender(),
|
||||
_log = Logger('AndroidIsolateHandler'),
|
||||
super();
|
||||
final BackgroundServiceDataSender _dataSender;
|
||||
final MethodChannel _channel;
|
||||
final Logger _log;
|
||||
|
||||
@override
|
||||
Future<void> attach(
|
||||
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
|
||||
) async {
|
||||
Future<void> attach(Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent) async {
|
||||
_channel.setMethodCallHandler((MethodCall call) async {
|
||||
final args = call.arguments as String;
|
||||
await handleIsolateEvent(jsonDecode(args) as Map<String, dynamic>);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<void> start(
|
||||
Future<void> Function(String initialLocale) entrypoint,
|
||||
Future<void> Function() entrypoint,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleUIEvent,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
|
||||
String initialLocale,
|
||||
) async {
|
||||
_log.finest('Called start');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final androidEntrypointHandle =
|
||||
PluginUtilities.getCallbackHandle(androidEntrypoint)!.toRawHandle();
|
||||
final androidEntrypointHandle = PluginUtilities.getCallbackHandle(androidEntrypoint)!.toRawHandle();
|
||||
_log.finest('androidEntrypointHandle: $androidEntrypointHandle');
|
||||
await _channel.invokeMethod<void>('configure', <dynamic>[
|
||||
androidEntrypointHandle,
|
||||
jsonEncode({
|
||||
'genericEntrypoint':
|
||||
PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(),
|
||||
'eventHandle':
|
||||
PluginUtilities.getCallbackHandle(handleUIEvent)!.toRawHandle(),
|
||||
'initialLocale': initialLocale,
|
||||
'genericEntrypoint': PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(),
|
||||
'eventHandle': PluginUtilities.getCallbackHandle(handleUIEvent)!.toRawHandle()
|
||||
}),
|
||||
]);
|
||||
|
||||
|
9
packages/moxplatform_android/lib/src/media_android.dart
Normal file
9
packages/moxplatform_android/lib/src/media_android.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'package:media_scanner/media_scanner.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
class AndroidMediaScannerImplementation extends MediaScannerImplementation {
|
||||
@override
|
||||
void scanFile(String path) {
|
||||
MediaScanner.loadMedia(path: path);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
class AndroidPlatformImplementation extends PlatformImplementation {
|
||||
@override
|
||||
Future<String> getCacheDataPath() {
|
||||
return MoxplatformInterface.api.getCacheDataPath();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getPersistentDataPath() {
|
||||
return MoxplatformInterface.api.getPersistentDataPath();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isIgnoringBatteryOptimizations() {
|
||||
return MoxplatformInterface.api.isIgnoringBatteryOptimizations();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> openBatteryOptimisationSettings() {
|
||||
return MoxplatformInterface.api.openBatteryOptimisationSettings();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> generateVideoThumbnail(
|
||||
String src,
|
||||
String dest,
|
||||
int width,
|
||||
) async {
|
||||
return MoxplatformInterface.api.generateVideoThumbnail(src, dest, width);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> pickFiles(FilePickerType type, bool pickMultiple) async {
|
||||
final result = await MoxplatformInterface.api.pickFiles(type, pickMultiple);
|
||||
return result.cast<String>();
|
||||
}
|
||||
}
|
@ -1,17 +1,15 @@
|
||||
import 'package:moxplatform_android/src/contacts_android.dart';
|
||||
import 'package:moxplatform_android/src/crypto_android.dart';
|
||||
import 'package:moxplatform_android/src/isolate_android.dart';
|
||||
import 'package:moxplatform_android/src/platform_android.dart';
|
||||
import 'package:moxplatform_android/src/media_android.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
|
||||
class MoxplatformAndroidPlugin extends MoxplatformInterface {
|
||||
static void registerWith() {
|
||||
// ignore: avoid_print
|
||||
print('MoxplatformAndroidPlugin: Registering implementation');
|
||||
MoxplatformInterface.contacts = AndroidContactsImplementation();
|
||||
MoxplatformInterface.crypto = AndroidCryptographyImplementation();
|
||||
MoxplatformInterface.handler = AndroidIsolateHandler();
|
||||
MoxplatformInterface.platform = AndroidPlatformImplementation();
|
||||
MoxplatformInterface.media = AndroidMediaScannerImplementation();
|
||||
MoxplatformInterface.crypto = AndroidCryptographyImplementation();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -4,18 +4,19 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxlib/awaitabledatasender.dart';
|
||||
import 'package:moxplatform/moxplatform.dart';
|
||||
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class AndroidBackgroundService extends BackgroundService {
|
||||
|
||||
AndroidBackgroundService()
|
||||
: _log = Logger('AndroidBackgroundService'),
|
||||
super();
|
||||
: _log = Logger('AndroidBackgroundService'),
|
||||
super();
|
||||
|
||||
@internal
|
||||
static const MethodChannel channel =
|
||||
MethodChannel('me.polynom.moxplatform_android_bg');
|
||||
static const MethodChannel channel = MethodChannel('me.polynom.moxplatform_android_bg');
|
||||
final Logger _log;
|
||||
|
||||
@override
|
||||
@ -27,21 +28,20 @@ class AndroidBackgroundService extends BackgroundService {
|
||||
}
|
||||
|
||||
@override
|
||||
void sendEvent(BackgroundEvent event, {String? id}) {
|
||||
void sendEvent(BackgroundEvent event, { String? id }) {
|
||||
final data = DataWrapper(
|
||||
id ?? const Uuid().v4(),
|
||||
event,
|
||||
);
|
||||
// NOTE: *S*erver to *F*oreground
|
||||
_log.fine('S2F: ${data.toJson()}');
|
||||
_log.fine('S2F: ${data.toJson().toString()}');
|
||||
channel.invokeMethod<void>('sendData', jsonEncode(data.toJson()));
|
||||
}
|
||||
|
||||
@override
|
||||
void init(
|
||||
Future<void> Function(String initialLocale) entrypoint,
|
||||
Future<void> Function() entrypoint,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleEvent,
|
||||
String initialLocale,
|
||||
) {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@ -54,11 +54,11 @@ class AndroidBackgroundService extends BackgroundService {
|
||||
final args = call.arguments! as String;
|
||||
await handleEvent(jsonDecode(args) as Map<String, dynamic>);
|
||||
});
|
||||
|
||||
|
||||
setNotification('Moxxy', 'Preparing...');
|
||||
|
||||
_log.finest('Running...');
|
||||
|
||||
entrypoint(initialLocale);
|
||||
entrypoint();
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
name: moxplatform_android
|
||||
description: Android implementation of moxplatform
|
||||
version: 0.1.22
|
||||
version: 0.1.15
|
||||
homepage: https://codeberg.org/moxxy/moxplatform
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <4.0.0"
|
||||
sdk: ">=2.16.0 <3.0.0"
|
||||
flutter: ">=2.10.0"
|
||||
|
||||
flutter:
|
||||
@ -22,17 +22,18 @@ dependencies:
|
||||
sdk: flutter
|
||||
get_it: ^7.2.0
|
||||
logging: ^1.0.2
|
||||
media_scanner: ^2.0.0
|
||||
meta: ^1.7.0
|
||||
moxlib:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.2.0
|
||||
version: ^0.1.4
|
||||
|
||||
moxplatform:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.1.17+6
|
||||
version: ^0.1.15
|
||||
moxplatform_platform_interface:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.1.22
|
||||
version: ^0.1.15
|
||||
|
||||
plugin_platform_interface: ^2.1.2
|
||||
uuid: ^3.0.5
|
||||
@ -40,5 +41,4 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
pigeon: 10.1.4
|
||||
very_good_analysis: ^3.0.1
|
||||
very_good_analysis: ^2.4.0
|
||||
|
@ -1,49 +1,3 @@
|
||||
## 0.1.22
|
||||
|
||||
- **FIX**(repo): Remove notification examples.
|
||||
- **FEAT**(interface,android): Allow passing an initial locale to the service.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
## 0.1.21
|
||||
|
||||
- **FIX**(repo): Remove notification examples.
|
||||
- **FEAT**(base,interface,android): Move more logic to Moxxy.
|
||||
- **FEAT**(android,interface): Implement video thumbnail generation.
|
||||
|
||||
## 0.1.20
|
||||
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
## 0.1.19
|
||||
|
||||
- **FEAT**(android,interface): Handle battery optimisation.
|
||||
|
||||
## 0.1.18
|
||||
|
||||
- **FIX**: Format and lint.
|
||||
- **FIX**: Add payload to all intents.
|
||||
- **FEAT**: Move recordSentMessage to pigeon.
|
||||
- **FEAT**: Move the crypto APIs to pigeon.
|
||||
- **FEAT**: Allow the sender's data being null.
|
||||
- **FEAT**: Allow attaching arbitrary data to the notification.
|
||||
- **FEAT**: Allow showing regular notifications.
|
||||
- **FEAT**: Color in the notification silhouette.
|
||||
- **FEAT**: Allow setting the self-avatar.
|
||||
- **FEAT**: Take care of i18n.
|
||||
|
||||
## 0.1.17+1
|
||||
|
||||
- Update a dependency to the latest release.
|
||||
|
||||
## 0.1.17
|
||||
|
||||
- **FIX**: Fix typecasting issue.
|
||||
- **FEAT**: Add an API for creating direct share shortcuts.
|
||||
- **FEAT**: Migrate to moxlib 0.2.0.
|
||||
- **FEAT**: I forgot to bump dependency versions.
|
||||
- **FEAT**: Also hash the file on encryption and decryption.
|
||||
|
||||
## 0.1.11+2
|
||||
|
||||
- **REFACTOR**: Make version constraints looser.
|
||||
|
@ -1,13 +1,10 @@
|
||||
library moxplatform_platform_interface;
|
||||
|
||||
export 'src/api.g.dart';
|
||||
export 'src/contacts.dart';
|
||||
export 'src/contacts_stub.dart';
|
||||
export 'src/crypto.dart';
|
||||
export 'src/crypto_stub.dart';
|
||||
export 'src/interface.dart';
|
||||
export 'src/isolate.dart';
|
||||
export 'src/isolate_stub.dart';
|
||||
export 'src/platform.dart';
|
||||
export 'src/platform_stub.dart';
|
||||
export 'src/media.dart';
|
||||
export 'src/media_stub.dart';
|
||||
export 'src/service.dart';
|
||||
|
@ -1,338 +0,0 @@
|
||||
// Autogenerated from Pigeon (v10.1.4), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
||||
|
||||
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
enum CipherAlgorithm {
|
||||
aes128GcmNoPadding,
|
||||
aes256GcmNoPadding,
|
||||
aes256CbcPkcs7,
|
||||
}
|
||||
|
||||
enum FallbackIconType {
|
||||
none,
|
||||
person,
|
||||
notes,
|
||||
}
|
||||
|
||||
enum FilePickerType {
|
||||
image,
|
||||
video,
|
||||
imageAndVideo,
|
||||
generic,
|
||||
}
|
||||
|
||||
class CryptographyResult {
|
||||
CryptographyResult({
|
||||
required this.plaintextHash,
|
||||
required this.ciphertextHash,
|
||||
});
|
||||
|
||||
Uint8List plaintextHash;
|
||||
|
||||
Uint8List ciphertextHash;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
plaintextHash,
|
||||
ciphertextHash,
|
||||
];
|
||||
}
|
||||
|
||||
static CryptographyResult decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return CryptographyResult(
|
||||
plaintextHash: result[0]! as Uint8List,
|
||||
ciphertextHash: result[1]! as Uint8List,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MoxplatformApiCodec extends StandardMessageCodec {
|
||||
const _MoxplatformApiCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is CryptographyResult) {
|
||||
buffer.putUint8(128);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 128:
|
||||
return CryptographyResult.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MoxplatformApi {
|
||||
/// Constructor for [MoxplatformApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
MoxplatformApi({BinaryMessenger? binaryMessenger})
|
||||
: _binaryMessenger = binaryMessenger;
|
||||
final BinaryMessenger? _binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> codec = _MoxplatformApiCodec();
|
||||
|
||||
/// Platform APIs
|
||||
Future<String> getPersistentDataPath() async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(null) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as String?)!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getCacheDataPath() async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getCacheDataPath', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(null) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as String?)!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openBatteryOptimisationSettings() async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.openBatteryOptimisationSettings', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(null) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> isIgnoringBatteryOptimizations() async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.isIgnoringBatteryOptimizations', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(null) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as bool?)!;
|
||||
}
|
||||
}
|
||||
|
||||
/// Contacts APIs
|
||||
Future<void> recordSentMessage(String arg_name, String arg_jid, String? arg_avatarPath, FallbackIconType arg_fallbackIcon) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.recordSentMessage', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_name, arg_jid, arg_avatarPath, arg_fallbackIcon.index]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cryptography APIs
|
||||
Future<CryptographyResult?> encryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as CryptographyResult?);
|
||||
}
|
||||
}
|
||||
|
||||
Future<CryptographyResult?> decryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as CryptographyResult?);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List?> hashFile(String arg_sourcePath, String arg_hashSpec) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_sourcePath, arg_hashSpec]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as Uint8List?);
|
||||
}
|
||||
}
|
||||
|
||||
/// Media APIs
|
||||
Future<bool> generateVideoThumbnail(String arg_src, String arg_dest, int arg_maxWidth) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.generateVideoThumbnail', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_src, arg_dest, arg_maxWidth]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as bool?)!;
|
||||
}
|
||||
}
|
||||
|
||||
/// Picker
|
||||
Future<List<String?>> pickFiles(FilePickerType arg_type, bool arg_pickMultiple) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.pickFiles', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final List<Object?>? replyList =
|
||||
await channel.send(<Object?>[arg_type.index, arg_pickMultiple]) as List<Object?>?;
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (replyList[0] as List<Object?>?)!.cast<String?>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
|
||||
// Wrapper around various contact APIs.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class ContactsImplementation {
|
||||
Future<void> recordSentMessage(
|
||||
String name,
|
||||
String jid, {
|
||||
String? avatarPath,
|
||||
FallbackIconType fallbackIcon = FallbackIconType.none,
|
||||
});
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
import 'package:moxplatform_platform_interface/src/contacts.dart';
|
||||
|
||||
class StubContactsImplementation extends ContactsImplementation {
|
||||
@override
|
||||
Future<void> recordSentMessage(
|
||||
String name,
|
||||
String jid, {
|
||||
String? avatarPath,
|
||||
FallbackIconType fallbackIcon = FallbackIconType.none,
|
||||
}) async {}
|
||||
}
|
@ -1,5 +1,27 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
|
||||
enum CipherAlgorithm {
|
||||
aes128GcmNoPadding,
|
||||
aes256GcmNoPadding,
|
||||
aes256CbcPkcs7,
|
||||
}
|
||||
|
||||
extension CipherAlgorithmToIntExtension on CipherAlgorithm {
|
||||
int toInt() {
|
||||
switch (this) {
|
||||
case CipherAlgorithm.aes128GcmNoPadding: return 0;
|
||||
case CipherAlgorithm.aes256GcmNoPadding: return 1;
|
||||
case CipherAlgorithm.aes256CbcPkcs7: return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CryptographyResult {
|
||||
|
||||
const CryptographyResult(this.plaintextHash, this.ciphertextHash);
|
||||
final Uint8List plaintextHash;
|
||||
final Uint8List ciphertextHash;
|
||||
}
|
||||
|
||||
/// Wrapper around platform-native cryptography APIs
|
||||
abstract class CryptographyImplementation {
|
||||
@ -8,32 +30,18 @@ abstract class CryptographyImplementation {
|
||||
/// Note that this function runs off-thread as to not block the UI thread.
|
||||
///
|
||||
/// Resolves to true if the encryption was successful. Resolves to fale on failure.
|
||||
Future<CryptographyResult?> encryptFile(
|
||||
String sourcePath,
|
||||
String destPath,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
CipherAlgorithm algorithm,
|
||||
String hashSpec,
|
||||
);
|
||||
Future<CryptographyResult?> encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
|
||||
|
||||
/// Decrypt the file at [sourcePath] using [algorithm] and write the result back to
|
||||
/// [destPath]. [hashSpec] is the name of the Hash function to use, i.e. "SHA-256".
|
||||
/// Note that this function runs off-thread as to not block the UI thread.
|
||||
///
|
||||
///
|
||||
/// Resolves to true if the encryption was successful. Resolves to fale on failure.
|
||||
Future<CryptographyResult?> decryptFile(
|
||||
String sourcePath,
|
||||
String destPath,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
CipherAlgorithm algorithm,
|
||||
String hashSpec,
|
||||
);
|
||||
Future<CryptographyResult?> decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
|
||||
|
||||
/// Hashes the file at [sourcePath] using the Hash function with name [hashSpec].
|
||||
/// Hashes the file at [path] using the Hash function with name [hashSpec].
|
||||
/// Note that this function runs off-thread as to not block the UI thread.
|
||||
///
|
||||
/// Returns the hash of the file.
|
||||
Future<Uint8List?> hashFile(String sourcePath, String hashSpec);
|
||||
Future<Uint8List?> hashFile(String path, String hashSpec);
|
||||
}
|
||||
|
@ -1,34 +1,19 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
import 'package:moxplatform_platform_interface/src/crypto.dart';
|
||||
|
||||
class StubCryptographyImplementation extends CryptographyImplementation {
|
||||
@override
|
||||
Future<CryptographyResult?> encryptFile(
|
||||
String sourcePath,
|
||||
String destPath,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
CipherAlgorithm algorithm,
|
||||
String hashSpec,
|
||||
) async {
|
||||
Future<CryptographyResult?> encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CryptographyResult?> decryptFile(
|
||||
String sourcePath,
|
||||
String destPath,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
CipherAlgorithm algorithm,
|
||||
String hashSpec,
|
||||
) async {
|
||||
Future<CryptographyResult?> decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async {
|
||||
Future<Uint8List?> hashFile(String path, String hashSpec) async {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,19 @@
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
import 'package:moxplatform_platform_interface/src/contacts.dart';
|
||||
import 'package:moxplatform_platform_interface/src/contacts_stub.dart';
|
||||
import 'package:moxplatform_platform_interface/src/crypto.dart';
|
||||
import 'package:moxplatform_platform_interface/src/crypto_stub.dart';
|
||||
import 'package:moxplatform_platform_interface/src/isolate.dart';
|
||||
import 'package:moxplatform_platform_interface/src/isolate_stub.dart';
|
||||
import 'package:moxplatform_platform_interface/src/platform.dart';
|
||||
import 'package:moxplatform_platform_interface/src/platform_stub.dart';
|
||||
import 'package:moxplatform_platform_interface/src/media.dart';
|
||||
import 'package:moxplatform_platform_interface/src/media_stub.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
abstract class MoxplatformInterface extends PlatformInterface {
|
||||
MoxplatformInterface() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static MoxplatformApi api = MoxplatformApi();
|
||||
|
||||
|
||||
static IsolateHandler handler = StubIsolateHandler();
|
||||
static MediaScannerImplementation media = StubMediaScannerImplementation();
|
||||
static CryptographyImplementation crypto = StubCryptographyImplementation();
|
||||
static ContactsImplementation contacts = StubContactsImplementation();
|
||||
static PlatformImplementation platform = StubPlatformImplementation();
|
||||
|
||||
/// Return the current platform name.
|
||||
Future<String?> getPlatformName();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxlib/awaitabledatasender.dart';
|
||||
|
||||
/// A class abstracting the interaction between the UI isolate and the background
|
||||
/// service, which is either a regular isolate or an Android foreground service.
|
||||
@ -8,19 +8,17 @@ abstract class IsolateHandler {
|
||||
/// [entrypoint] is the entrypoint that is run inside the new isolate.
|
||||
/// [handleUIEvent] is a handler function that is called when the isolate receives data from the UI.
|
||||
/// [handleIsolateEvent] is a handler function that is called when the UI receives data from the service.
|
||||
/// [initialLocale] the locale to pass to the background service when first starting.
|
||||
Future<void> start(
|
||||
Future<void> Function(String initialLocale) entrypoint,
|
||||
Future<void> Function() entrypoint,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleUIEvent,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
|
||||
String initialLocale,
|
||||
);
|
||||
|
||||
/// Make sure that the UI event handler is registered without starting the isolate.
|
||||
Future<void> attach(
|
||||
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
|
||||
);
|
||||
|
||||
|
||||
/// Return true if the background service is running. False if it's not.
|
||||
Future<bool> isRunning();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxlib/awaitabledatasender.dart';
|
||||
import 'package:moxplatform_platform_interface/src/isolate.dart';
|
||||
|
||||
class StubDataSender extends AwaitableDataSender {
|
||||
@ -19,13 +19,12 @@ class StubIsolateHandler extends IsolateHandler {
|
||||
// ignore: avoid_print
|
||||
print('STUB ATTACHED!!!!!!');
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<void> start(
|
||||
Future<void> Function(String initialLocale) entrypoint,
|
||||
Future<void> Function() entrypoint,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleUIEvent,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleIsolateEvent,
|
||||
String initialLocale,
|
||||
) async {
|
||||
// ignore: avoid_print
|
||||
print('STUB STARTED!!!!!!');
|
||||
|
@ -0,0 +1,6 @@
|
||||
/// Wrapper around platform-specific media scanning
|
||||
// ignore: one_member_abstracts
|
||||
abstract class MediaScannerImplementation {
|
||||
/// Let the platform-specific media scanner scan the file at [path].
|
||||
void scanFile(String path);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import 'package:moxplatform_platform_interface/src/media.dart';
|
||||
|
||||
class StubMediaScannerImplementation extends MediaScannerImplementation {
|
||||
@override
|
||||
void scanFile(String path) {}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
|
||||
abstract class PlatformImplementation {
|
||||
/// Returns the path where persistent data should be stored.
|
||||
Future<String> getPersistentDataPath();
|
||||
|
||||
/// Returns the path where cache data should be stored.
|
||||
Future<String> getCacheDataPath();
|
||||
|
||||
/// Returns whether the app is battery-optimised (false) or
|
||||
/// excluded from battery savings (true).
|
||||
Future<bool> isIgnoringBatteryOptimizations();
|
||||
|
||||
/// Opens the page for battery optimisations. If not supported on the
|
||||
/// platform, does nothing.
|
||||
Future<void> openBatteryOptimisationSettings();
|
||||
|
||||
/// Attempt to generate a thumbnail for the video file at [src], scale it, while keeping the
|
||||
/// aspect ratio in tact to [width], and write it to [dest]. If we were successful, returns true.
|
||||
/// If no thumbnail was generated, returns false.
|
||||
Future<bool> generateVideoThumbnail(String src, String dest, int width);
|
||||
|
||||
Future<List<String>> pickFiles(FilePickerType type, bool pickMultiple);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import 'package:moxplatform_platform_interface/src/api.g.dart';
|
||||
import 'package:moxplatform_platform_interface/src/platform.dart';
|
||||
|
||||
class StubPlatformImplementation extends PlatformImplementation {
|
||||
/// Returns the path where persistent data should be stored.
|
||||
@override
|
||||
Future<String> getPersistentDataPath() async => '';
|
||||
|
||||
/// Returns the path where cache data should be stored.
|
||||
@override
|
||||
Future<String> getCacheDataPath() async => '';
|
||||
|
||||
@override
|
||||
Future<bool> isIgnoringBatteryOptimizations() async => false;
|
||||
|
||||
@override
|
||||
Future<void> openBatteryOptimisationSettings() async {}
|
||||
|
||||
@override
|
||||
Future<bool> generateVideoThumbnail(
|
||||
String src,
|
||||
String dest,
|
||||
int width,
|
||||
) async =>
|
||||
false;
|
||||
|
||||
@override
|
||||
Future<List<String>> pickFiles(FilePickerType type, bool pickMultiple) async => [];
|
||||
}
|
@ -5,14 +5,13 @@ abstract class BackgroundService {
|
||||
void setNotification(String title, String body);
|
||||
|
||||
/// Send data from the background service to the UI.
|
||||
void sendEvent(BackgroundEvent event, {String? id});
|
||||
void sendEvent(BackgroundEvent event, { String? id });
|
||||
|
||||
/// Called before [entrypoint]. Sets up whatever it needs to set up.
|
||||
/// [handleEvent] is a function that is called whenever the service receives
|
||||
/// data.
|
||||
void init(
|
||||
Future<void> Function(String initialLocale) entrypoint,
|
||||
Future<void> Function() entrypoint,
|
||||
Future<void> Function(Map<String, dynamic>? data) handleEvent,
|
||||
String initialLocale,
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
name: moxplatform_platform_interface
|
||||
description: A common platform interface for the my_plugin plugin.
|
||||
version: 0.1.22
|
||||
version: 0.1.15
|
||||
homepage: https://codeberg.org/moxxy/moxplatform
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
sdk: ">=2.16.0 <3.0.0"
|
||||
flutter: ">=2.10.0"
|
||||
|
||||
dependencies:
|
||||
@ -14,14 +14,14 @@ dependencies:
|
||||
|
||||
moxlib:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.2.0
|
||||
version: ^0.1.4
|
||||
moxplatform:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.1.17+6
|
||||
version: ^0.1.15
|
||||
|
||||
plugin_platform_interface: ^2.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
very_good_analysis: ^3.0.1
|
||||
very_good_analysis: ^2.4.0
|
||||
|
@ -1,64 +0,0 @@
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
@ConfigurePigeon(
|
||||
PigeonOptions(
|
||||
dartOut: 'packages/moxplatform_platform_interface/lib/src/api.g.dart',
|
||||
//kotlinOut: 'packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Notifications.g.kt',
|
||||
//kotlinOptions: KotlinOptions(
|
||||
// package: 'me.polynom.moxplatform_android',
|
||||
//),
|
||||
javaOut: 'packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java',
|
||||
javaOptions: JavaOptions(
|
||||
package: 'me.polynom.moxplatform_android',
|
||||
),
|
||||
),
|
||||
)
|
||||
enum CipherAlgorithm {
|
||||
aes128GcmNoPadding,
|
||||
aes256GcmNoPadding,
|
||||
aes256CbcPkcs7;
|
||||
}
|
||||
|
||||
class CryptographyResult {
|
||||
const CryptographyResult(this.plaintextHash, this.ciphertextHash);
|
||||
final Uint8List plaintextHash;
|
||||
final Uint8List ciphertextHash;
|
||||
}
|
||||
|
||||
// The type of icon to use when no avatar path is provided.
|
||||
enum FallbackIconType {
|
||||
none,
|
||||
person,
|
||||
notes;
|
||||
}
|
||||
|
||||
enum FilePickerType {
|
||||
image,
|
||||
video,
|
||||
imageAndVideo,
|
||||
generic,
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class MoxplatformApi {
|
||||
/// Platform APIs
|
||||
String getPersistentDataPath();
|
||||
String getCacheDataPath();
|
||||
void openBatteryOptimisationSettings();
|
||||
bool isIgnoringBatteryOptimizations();
|
||||
|
||||
/// Contacts APIs
|
||||
void recordSentMessage(String name, String jid, String? avatarPath, FallbackIconType fallbackIcon);
|
||||
|
||||
/// Cryptography APIs
|
||||
@async CryptographyResult? encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
|
||||
@async CryptographyResult? decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec);
|
||||
@async Uint8List? hashFile(String sourcePath, String hashSpec);
|
||||
|
||||
/// Media APIs
|
||||
bool generateVideoThumbnail(String src, String dest, int maxWidth);
|
||||
|
||||
/// Picker
|
||||
@async
|
||||
List<String> pickFiles(FilePickerType type, bool pickMultiple);
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
name: moxplatform_workspace
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
dev_dependencies:
|
||||
melos: ^3.1.1
|
||||
pigeon: 10.1.4
|
||||
very_good_analysis: ^3.0.1
|
Reference in New Issue
Block a user