Skip to content
This repository has been archived by the owner on Oct 19, 2022. It is now read-only.

Commit

Permalink
Push support android (#559)
Browse files Browse the repository at this point in the history
* Cleaned up and hardened BloC logic #501 OT-779
Fixed method channel names
Fixed logger usage
Renamed notification_manager.dart to display_notification_manager.dart
Added async suffix
Updated libraries involved in push and notifications
Fixed some names and const declarations

* Moved key generation into the dart part #501 OT-779
Key handling includes generating, persisting, getting, converting
Refactored the method channel handling on Android
Refactored the method channel handling on Dart / Flutter side
Cleaned up Android native part
Renaming and wrapping to better understand sharing flows

* Update Android components and try push workarounds #501 OT-779
Fixed MethodChannels.java typo
Updated gradle
Update libs
Added more push callback for debugging

* Updated background_fetch, adjust plugin #501 OT-779

* Fix logging

* Adjust to new plugin init param

* Fix logging

* Only recreate LocalNotificationManager if called from background
Renamed stuff

* Use decrypt in memory

* Fix background refresh

* testing with in mem decryption

* Update firebase

* post rebase fixes

* first addition of NotificationService extension

* Rebase fixes

* Cleanup

* Cleanup and remove not working workarounds

* Fix typo

* Post rebase fixes

* SecurityHelper additions

* Adjust stuff

Co-authored-by: Frank Gregor <[email protected]>
  • Loading branch information
Boehrsi and Frank Gregor authored Jun 5, 2020
1 parent 61d4e3d commit 5c19aef
Show file tree
Hide file tree
Showing 70 changed files with 2,102 additions and 915 deletions.
5 changes: 2 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,10 @@ flutter {
}

dependencies {
implementation 'com.google.crypto.tink:apps-webpush:1.3.0-rc1'
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.crypto.tink:apps-webpush:1.3.0'
implementation 'org.bouncycastle:bcprov-jdk16:1.46'

testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'

androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
Expand Down
4 changes: 2 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
tools:replace="android:label">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<!-- Specify that the launch screen should continue being displayed until Flutter renders its first frame. -->
<meta-data
Expand Down
108 changes: 41 additions & 67 deletions android/app/src/main/java/com/openxchange/oxcoi/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,147 +48,121 @@
import android.os.Bundle;
import android.util.Base64;

import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;

import java.io.File;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
private Map<String, String> sharedData = new HashMap<>();
private String startString = "";
private static final String SHARED_MIME_TYPE = "shared_mime_type";
private static final String SHARED_TEXT = "shared_text";
private static final String SHARED_PATH = "shared_path";
private static final String SHARED_FILE_NAME = "shared_file_name";
// TODO create constants for channel methods
private static final String INTENT_CHANNEL_NAME = "oxcoi.intent";
// TODO create constants for channel methods
private static final String SECURITY_CHANNEL_NAME = "oxcoi.security";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIntent(getIntent());
cacheDataFromPlatform(getIntent());
}

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
DartExecutor dartExecutor = flutterEngine.getDartExecutor();
setupSharingMethodChannel(dartExecutor);
SecurityHelper securityHelper = new SecurityHelper(this);
setupSecurityMethodChannel(dartExecutor, securityHelper);
setupSecurityMethodChannel(dartExecutor);
}

@Override
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
cacheDataFromPlatform(intent);
}

private void setupSharingMethodChannel(DartExecutor dartExecutor) {
new MethodChannel(dartExecutor, INTENT_CHANNEL_NAME).setMethodCallHandler(
new io.flutter.plugin.common.MethodChannel(dartExecutor, MethodChannel.Sharing.NAME).setMethodCallHandler(
(call, result) -> {
if (call.method.contentEquals("getSharedData")) {
if (call.method.contentEquals(MethodChannel.Sharing.Methods.GET_SHARE_DATA)) {
result.success(sharedData);
sharedData.clear();
} else if (call.method.contentEquals("getInitialLink")) {
} else if (call.method.contentEquals(MethodChannel.Sharing.Methods.GET_INITIAL_LINK)) {
if (startString != null && !startString.isEmpty()) {
result.success(startString);
startString = "";
} else {
result.success(null);
}
} else if (call.method.contentEquals("sendSharedData")) {
shareFile(call.arguments);
} else if (call.method.contentEquals(MethodChannel.Sharing.Methods.SEND_SHARE_DATA)) {
shareDataWithPlatform(call.arguments);
result.success(null);
}
});
}

private void setupSecurityMethodChannel(DartExecutor dartExecutor, SecurityHelper securityHelper) {
new MethodChannel(dartExecutor, SECURITY_CHANNEL_NAME).setMethodCallHandler(
private void setupSecurityMethodChannel(DartExecutor dartExecutor) {
SecurityHelper securityHelper = new SecurityHelper();
new io.flutter.plugin.common.MethodChannel(dartExecutor, MethodChannel.Security.NAME).setMethodCallHandler(
(call, result) -> {
if (call.method.contentEquals("generateSecrets")) {
KeyPair keyPair = securityHelper.generateKey();
if (keyPair != null) {
securityHelper.persistKeyPair(keyPair);
} else {
throw new IllegalStateException("Key pair is empty");
}
String authSecret = securityHelper.generateAuthSecret();
if (authSecret != null && !authSecret.isEmpty()) {
securityHelper.persisAuthSecret(authSecret);
} else {
throw new IllegalStateException("Auth secret is empty");
}
result.success(null);
} else if (call.method.contentEquals("getKey")) {
String publicKeyBase64 = securityHelper.getPublicKeyBase64();
result.success(publicKeyBase64);
} else if (call.method.contentEquals("getAuthSecret")) {
String authSecret = securityHelper.getAuthSecretFromPersistedData();
result.success(authSecret);
} else if (call.method.contentEquals("decrypt")) {
String input = call.argument("input");
byte[] inputBytes = Base64.decode(input, Base64.DEFAULT);
String decryptMessage = securityHelper.decryptMessage(inputBytes);
if (call.method.contentEquals(MethodChannel.Security.Methods.DECRYPT)) {
String encryptedBase64Content = call.argument(MethodChannel.Security.Arguments.CONTENT);
String privateKeyBase64 = call.argument(MethodChannel.Security.Arguments.PRIVATE_KEY);
String publicKeyBase64 = call.argument(MethodChannel.Security.Arguments.PUBLIC_KEY);
String authBase64 = call.argument(MethodChannel.Security.Arguments.AUTH);
byte[] inputBytes = Base64.decode(encryptedBase64Content, Base64.DEFAULT);
String decryptMessage = securityHelper.decryptMessage(inputBytes, privateKeyBase64, publicKeyBase64, authBase64);
result.success(decryptMessage);
}

});
}

@Override
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}

private void handleIntent(Intent intent) {
private void cacheDataFromPlatform(Intent intent) {
String action = intent.getAction();
String type = intent.getType();
Uri data = intent.getData();

if (Intent.ACTION_SEND.equals(action) && type != null) {
if (type.startsWith("text/")) {
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
sharedData.put(SHARED_MIME_TYPE, type);
sharedData.put(SHARED_TEXT, text);
sharedData.put(MethodChannel.Sharing.Arguments.MIME_TYPE, type);
sharedData.put(MethodChannel.Sharing.Arguments.TEXT, text);
} else if (type.startsWith("application/") || type.startsWith("audio/") || type.startsWith("image/") || type.startsWith("video/")) {
Uri uri = (Uri) Objects.requireNonNull(getIntent().getExtras()).get(Intent.EXTRA_STREAM);
if (uri == null) {
ClipData clipData = intent.getClipData();
ClipData.Item item = clipData.getItemAt(0);
uri = item.getUri();
if (clipData != null) {
ClipData.Item item = clipData.getItemAt(0);
uri = item.getUri();
}
}
if (uri != null) {
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
ShareHelper shareHelper = new ShareHelper();
String uriPath = shareHelper.getFilePathForUri(this, uri);
if (text != null && !text.isEmpty()) {
sharedData.put(SHARED_TEXT, text);
sharedData.put(MethodChannel.Sharing.Arguments.TEXT, text);
}
sharedData.put(SHARED_MIME_TYPE, type);
sharedData.put(SHARED_PATH, uriPath);
sharedData.put(SHARED_FILE_NAME, shareHelper.getFileName());
sharedData.put(MethodChannel.Sharing.Arguments.MIME_TYPE, type);
sharedData.put(MethodChannel.Sharing.Arguments.PATH, uriPath);
sharedData.put(MethodChannel.Sharing.Arguments.NAME, shareHelper.getFileName());
}
}
} else if (Intent.ACTION_VIEW.equals(action) && data != null) {
startString = data.toString();
}
}

private void shareFile(Object arguments) {
private void shareDataWithPlatform(Object arguments) {
@SuppressWarnings("unchecked")
HashMap<String, String> argsMap = (HashMap<String, String>) arguments;
String title = argsMap.get("title");
String path = argsMap.get("path");
String mimeType = argsMap.get("mimeType");
String text = argsMap.get("text");
String title = argsMap.get(MethodChannel.Sharing.Arguments.TITLE);
String path = argsMap.get(MethodChannel.Sharing.Arguments.PATH);
String mimeType = argsMap.get(MethodChannel.Sharing.Arguments.MIME_TYPE);
String text = argsMap.get(MethodChannel.Sharing.Arguments.TEXT);

Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType(mimeType);
Expand Down
80 changes: 80 additions & 0 deletions android/app/src/main/java/com/openxchange/oxcoi/MethodChannel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* OPEN-XCHANGE legal information
*
* All intellectual property rights in the Software are protected by
* international copyright laws.
*
*
* In some countries OX, OX Open-Xchange and open xchange
* as well as the corresponding Logos OX Open-Xchange and OX are registered
* trademarks of the OX Software GmbH group of companies.
* The use of the Logos is not covered by the Mozilla Public License 2.0 (MPL 2.0).
* Instead, you are allowed to use these Logos according to the terms and
* conditions of the Creative Commons License, Version 2.5, Attribution,
* Non-commercial, ShareAlike, and the interpretation of the term
* Non-commercial applicable to the aforementioned license is published
* on the web site https://www.open-xchange.com/terms-and-conditions/.
*
* Please make sure that third-party modules and libraries are used
* according to their respective licenses.
*
* Any modifications to this package must retain all copyright notices
* of the original copyright holder(s) for the original code used.
*
* After any such modifications, the original and derivative code shall remain
* under the copyright of the copyright holder(s) and/or original author(s) as stated here:
* https://www.open-xchange.com/legal/. The contributing author shall be
* given Attribution for the derivative code and a license granting use.
*
* Copyright (C) 2016-2020 OX Software GmbH
* Mail: [email protected]
*
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the Mozilla Public License 2.0
* for more details.
*/

package com.openxchange.oxcoi;

class MethodChannel {

abstract static class Security {
static final String NAME = "oxcoi.security";

abstract static class Methods {
static final String DECRYPT = "decrypt";
}

abstract static class Arguments {
static final String CONTENT = "encryptedBase64Content";
static final String PRIVATE_KEY = "privateKeyBase64";
static final String PUBLIC_KEY = "publicKeyBase64";
static final String AUTH = "authBase64";
}
}

abstract static class Sharing {
static final String NAME = "oxcoi.sharing";

abstract static class Methods {
static final String GET_SHARE_DATA = "getSharedData";
static final String SEND_SHARE_DATA = "sendSharedData";
static final String GET_INITIAL_LINK = "getInitialLink";
}

abstract static class Arguments {
static final String MIME_TYPE = "mimeType";
static final String TEXT = "text";
static final String PATH = "path";
static final String NAME = "fileName";
static final String TITLE = "title";
}
}
}

Loading

0 comments on commit 5c19aef

Please sign in to comment.