Skip to content

Commit

Permalink
Merge pull request #74 from lbr38/android
Browse files Browse the repository at this point in the history
Android
  • Loading branch information
lbr38 authored Nov 11, 2024
2 parents b393dff + c140006 commit 250de7e
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ todo
WIP
android/.idea/*
keystore.properties
app-release.aab
app-release.aab
19 changes: 18 additions & 1 deletion android/motionUI/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import java.util.Properties
import java.io.FileInputStream

plugins {
id 'com.android.application'
}

// Ici on importe les secrets à partir d'un fichier dédié (keystore.properties)
def keystorePropertiesFile = rootProject.file('keystore.properties')
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
signingConfigs {
release {
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
keyPassword keystoreProperties['keyPassword']
keyAlias keystoreProperties['keyAlias']
}
}
namespace 'com.example.motionui'
compileSdk 34

Expand All @@ -20,6 +36,7 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions {
Expand All @@ -29,11 +46,11 @@ android {
}

dependencies {

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
implementation "androidx.core:core-splashscreen:1.0.0"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
Expand Down
14 changes: 11 additions & 3 deletions android/motionUI/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Write to storage permissions -->
<!-- Permission to read files in external storage (Android 10 to 12) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Specific permissions for Android 13 (API 33+) depending on the type of media -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<application
android:allowBackup="true"
Expand All @@ -15,9 +22,11 @@
android:theme="@style/Theme.MotionUI"
android:usesCleartextTraffic="true"
tools:targetApi="31">

<activity
android:name=".Startup"
android:exported="false" />
<activity
android:name=".SplashScreen"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -27,8 +36,7 @@
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
android:exported="true"></activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
package com.example.motionui;

import androidx.appcompat.app.AppCompatActivity;
import android.widget.Toast;
import android.app.DownloadManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.DownloadListener;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.os.Build;
import android.webkit.CookieManager;
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;


/**
* MainActivity
* This is the main activity of the app (motionUI main page)
*/
public class MainActivity extends AppCompatActivity {

private static final int STORAGE_PERMISSION_REQUEST_CODE = 1001;
private WebView webView;
private String url;
// private Integer authTry = 0;
Expand Down Expand Up @@ -59,6 +70,12 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

/**
* Check that the app has the necessary permissions to access the storage
* This is required to download files (videos and images from motion)
*/
checkAndRequestStoragePermissions();

/**
* Retrieve motionUI URL from Startup activity
*/
Expand Down Expand Up @@ -181,6 +198,54 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request
}
});

/**
* Permit download of video files
*/
webView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
String fileName = "";

/**
* Try to extract the file name from the URL
* The URL should contain the file name as a query parameter, like this: http://example.com/media.php?id=xxxxx&filename=myfile.mp4
*/
try {
Uri uri = Uri.parse(url);
fileName = uri.getQueryParameter("filename");
} catch (Exception e) {
// Notify the user that the download failed because the file name could not be extracted
Toast.makeText(getApplicationContext(), "Download failed: error while extracting file name from URL", Toast.LENGTH_LONG).show();
}

Log.d("Download", "Downloading file: " + fileName);

// Check that the file name is not empty
if (fileName == null || fileName.isEmpty()) {
// Notify the user that the download failed because the file name could not be extracted
Toast.makeText(getApplicationContext(), "Download failed: could not extract file name from URL", Toast.LENGTH_LONG).show();
return;
}

/**
* Start the download
*/
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setMimeType(mimeType);
String cookies = CookieManager.getInstance().getCookie(url);
request.addRequestHeader("cookie", cookies);
request.addRequestHeader("User-Agent", userAgent);
request.setDescription("Downloading file...");
request.setTitle(fileName);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);

DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
downloadManager.enqueue(request);
}
});

/**
* Load motionUI URL in the WebView
*/
Expand All @@ -200,4 +265,60 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {

return super.onKeyDown(keyCode, event);
}

/**
* Check that the app has the necessary permissions to access the storage
*/
private void checkAndRequestStoragePermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13 and higher: request specific media permissions
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO) != PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO
},
STORAGE_PERMISSION_REQUEST_CODE);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10 to Android 12: use READ_EXTERNAL_STORAGE
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
STORAGE_PERMISSION_REQUEST_CODE);
}
} else {
// Android 9 and earlier: WRITE_EXTERNAL_STORAGE is required
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
STORAGE_PERMISSION_REQUEST_CODE);
}
}
}

/**
* This method is called after the user's response to the permissions
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) {
boolean allPermissionsGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false;
break;
}
}
if (!allPermissionsGranted) {
// One or more permissions were denied
Toast.makeText(this, "Write permission is denied. You will not be able to download files.", Toast.LENGTH_LONG).show();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.motionui;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;

public class SplashScreen extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_screen);

new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(SplashScreen.this, Startup.class);
startActivity(intent);
finish();

}
}, 500);
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
package com.example.motionui;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import android.util.Log;

// Encrypted SharedPreferences
// import androidx.security.crypto.EncryptedSharedPreferences;
// import androidx.security.crypto.MasterKeys;

/**
* Startup activity
* This is the first activity that is opened when the app is launched
* It contains a form to enter the URL of the motionUI server if it is not already saved in the app
* Then it redirects to the MainActivity (motionUI main page)
*/
public class Startup extends AppCompatActivity {

private Button button;
private EditText editText;
private String url;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SplashScreen"
android:background="@color/motionUIBlue">

<LinearLayout
android:layout_width="450px"
android:layout_height="450px"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5">

<ImageView
android:layout_width="450px"
android:layout_height="450px"
android:background="@drawable/motion">
</ImageView>
</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Enter motionUI server URL:"
android:text="Enter motion-UI server URL:"
android:textAlignment="center"
android:textSize="16dp"
android:textColor="@color/white" />
Expand Down
2 changes: 1 addition & 1 deletion android/motionUI/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<resources>
<string name="app_name">motionUI</string>
<string name="app_name">Motion-UI</string>
<color name="motionUIBlue">#112334</color>
<color name="motionUIGreen">#15bf7f</color>
</resources>
4 changes: 3 additions & 1 deletion www/public/resources/js/motion.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ function downloadMedia()

for (var n = 0; n < filesForDownload.length; n++) {
var download = filesForDownload[n];
temporaryDownloadLink.setAttribute('href', '/media?id=' + download.fileId);
// Set the href attribute to the file path, also include the filename for the android app to make sure it downloads the file with the correct name
temporaryDownloadLink.setAttribute('href', '/media?id=' + download.fileId + '&filename=' + download.filename);
// Set the download attribute to force download
temporaryDownloadLink.setAttribute('download', download.filename);

/**
Expand Down

0 comments on commit 250de7e

Please sign in to comment.