diff --git a/app/build.gradle b/app/build.gradle index c65b01d763b0d28230f03af8e2f0b82c8c54df46..6efc99a96ab7a13bf9f96309e83a5cfd218e4c5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,6 +53,9 @@ android { //includeAndroidResources = true } } + buildFeatures { + viewBinding true + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index be871995566a3c5de7fc4bbb8df3365b36fc9856..33e24a7cf9add50f45afd372847553ed42f23cd6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,95 +1,103 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index e0f762fcb4f2d104db8baa88938f26276ddbee85..2a66b9ddfe699683082cb968b6ee3f73695a7416 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -31,6 +31,9 @@ import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.resources.files.FileUtils; import java.io.File; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.Locale; import foundation.e.drive.receivers.ScreenOffReceiver; @@ -293,4 +296,26 @@ public abstract class CommonUtils { + "\n File can be read?: " + f.canRead() + "\n File can be written?: " + f.canWrite(); } + + /** + * Formatter class is not used since bytes passed by server are in SI unit aka 1kb = 1024byte + * https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java/3758880#3758880 + * + * @param bytes file/data size in bytes + * @return String in human readable format + */ + public static String humanReadableByteCountBin(long bytes) { + long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); + if (absB < 1024) { + return bytes + " B"; + } + long value = absB; + CharacterIterator ci = new StringCharacterIterator("KMGTPE"); + for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { + value >>= 10; + ci.next(); + } + value *= Long.signum(bytes); + return String.format(Locale.getDefault(),"%.1f %cB", value / 1024.0, ci.current()); + } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java new file mode 100644 index 0000000000000000000000000000000000000000..ee13697d5d61e901a041dd8e8b8daadfbd4231fd --- /dev/null +++ b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java @@ -0,0 +1,204 @@ +/* + * Copyright © ECORP SAS 2022. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.widgets; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.provider.Settings; +import android.widget.RemoteViews; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.UserInfo; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +import foundation.e.drive.R; +import foundation.e.drive.utils.CommonUtils; + +/** + * Implementation of App Widget functionality. + */ +public class EDriveWidget extends AppWidgetProvider { + private static final String webpage = + "https://esolutions.shop/ecloud-subscriptions/?username=%s&token=%s¤t-quota=%s&from=nextcloud"; + private static final String addAccountWebpage = "https://e.foundation/e-email-invite/"; + private static final String accountManagerPackageName = "foundation.e.accountmanager"; + private static final String getAccountManagerComponentName = accountManagerPackageName + + ".ui.setup.LoginActivity"; + private static final String SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type"; + private static final String ACCOUNT_PROVIDER_EELO = "eelo"; + + private final Calendar calender = Calendar.getInstance(); + private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); + private final GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); + private UserInfo userInfo = null; + private RemoteViews views = null; + + public void updateAppWidget(final Context context) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + final ComponentName provider = new ComponentName(context, getClass()); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(provider); + + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + + public void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, + final int appWidgetId) { + final AccountManager accountManager = AccountManager.get(context); + final Account account = CommonUtils.getAccount(context.getString(R.string.eelo_account_type), + accountManager); + final OwnCloudClient client = CommonUtils.getOwnCloudClient(account, context); + views = new RemoteViews(context.getPackageName(), R.layout.e_drive_widget); + + HandlerThread handlerThread = new HandlerThread("Network Request"); + handlerThread.start(); + Handler mHandler = new Handler(handlerThread.getLooper()); + + final EDriveWidgetCallback callback = new EDriveWidgetCallback() { + @Override + public void onComplete() { + onNetworkRequestCompleted(context, appWidgetManager, appWidgetId, client, account); + } + }; + + mHandler.post(new Runnable() { + @Override + public void run() { + if (client != null) { + RemoteOperationResult ocsResult = getRemoteUserInfoOperation.execute(client); + if (ocsResult.isSuccess() && ocsResult.getData() != null) { + userInfo = (UserInfo) ocsResult.getData().get(0); + } + } + callback.onComplete(); + } + }); + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // There may be multiple widgets active, so update all of them + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + + @Override + public void onEnabled(Context context) { + updateAppWidget(context); + + } + + @Override + public void onDisabled(Context context) { + // Enter relevant functionality for when the last widget is disabled + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) return; + if (Intent.ACTION_BOOT_COMPLETED.equals(action) || + AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(action)) { + updateAppWidget(context); + } + } + + private String dataForWeb(Long bytes) { + String space = CommonUtils.humanReadableByteCountBin(bytes); + String[] split = space.split(" "); + return Math.round(Double.parseDouble(split[0])) + split[1]; + } + + private Intent buildIntent(String name, String extra) { + Intent intent = new Intent(name); + if (!extra.isEmpty()) { + intent.setData(Uri.parse(extra)); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return intent; + } + + private void onNetworkRequestCompleted(Context context, AppWidgetManager appWidgetManager, + int appWidgetId, OwnCloudClient client, Account account) { + if (account == null || userInfo == null) { + views = new RemoteViews(context.getPackageName(), R.layout.e_drive_widget_login); + Intent accountIntent = buildIntent("", "") + .setComponent(new ComponentName(accountManagerPackageName, + getAccountManagerComponentName)) + .putExtra(SETUP_ACCOUNT_PROVIDER_TYPE, ACCOUNT_PROVIDER_EELO); + PendingIntent addAccountIntent = PendingIntent.getActivity(context, 0, + accountIntent, PendingIntent.FLAG_IMMUTABLE); + views.setOnClickPendingIntent(R.id.login, addAccountIntent); + PendingIntent pendingIntentNewAccount = PendingIntent.getActivity(context, 0, + buildIntent(Intent.ACTION_VIEW, addAccountWebpage), PendingIntent.FLAG_IMMUTABLE); + views.setOnClickPendingIntent(R.id.newAccount, pendingIntentNewAccount); + appWidgetManager.updateAppWidget(appWidgetId, views); + return; + } + + // Construct the RemoteViews object + double usedMB = userInfo.quota.used / 1024.0 / 1024.0; + double totalMB = userInfo.quota.total / 1024.0 / 1024.0; + + views.setTextViewText(R.id.email, userInfo.id); + if (!userInfo.alternateDisplayName.isEmpty()) { + views.setTextViewText(R.id.name, userInfo.alternateDisplayName); + } else { + views.setTextViewText(R.id.name, userInfo.displayName); + } + + views.setProgressBar(R.id.progress, (int) totalMB, (int) usedMB, false); + + views.setTextViewText(R.id.planName, context.getString(R.string.free_plan, + CommonUtils.humanReadableByteCountBin(userInfo.quota.total))); + + for (String group : userInfo.groups) { + if (group.contains("premium-")) { + views.setTextViewText(R.id.planName, context.getString(R.string.premium_plan, + group.split("-")[1])); + break; + } + } + views.setTextViewText(R.id.status, context.getString(R.string.progress_status, + CommonUtils.humanReadableByteCountBin(userInfo.quota.used), + CommonUtils.humanReadableByteCountBin(userInfo.quota.total))); + views.setTextViewText(R.id.sync, context.getString(R.string.last_synced, + sdf.format(calender.getTime()))); + + PendingIntent pendingIntentSettings = PendingIntent.getActivity(context, 0, + buildIntent(Settings.ACTION_SYNC_SETTINGS, ""), PendingIntent.FLAG_IMMUTABLE); + views.setOnClickPendingIntent(R.id.settings, pendingIntentSettings); + + PendingIntent pendingIntentUpgrade = PendingIntent.getActivity(context, 0, + buildIntent(Intent.ACTION_VIEW, String.format(webpage, userInfo.id, + client.getCredentials().getAuthToken(), dataForWeb(userInfo.quota.total))), + PendingIntent.FLAG_IMMUTABLE); + views.setOnClickPendingIntent(R.id.upgrade, pendingIntentUpgrade); + + // Instruct the widget manager to update the widget + appWidgetManager.updateAppWidget(appWidgetId, views); + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/widgets/EDriveWidgetCallback.java b/app/src/main/java/foundation/e/drive/widgets/EDriveWidgetCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..b0a2b428f3f5c4f69aae7c832a639fc19b671373 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/widgets/EDriveWidgetCallback.java @@ -0,0 +1,13 @@ +/* + * Copyright © ECORP SAS 2022. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package foundation.e.drive.widgets; + +public interface EDriveWidgetCallback { + void onComplete(); +} diff --git a/app/src/main/res/drawable/button_background.xml b/app/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..1c1a64eee77dcdd4c2aa2f6d37208daae639dc62 --- /dev/null +++ b/app/src/main/res/drawable/button_background.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cloud.xml b/app/src/main/res/drawable/ic_cloud.xml new file mode 100644 index 0000000000000000000000000000000000000000..3bffa01450258cc5f9522bbf480c1419237b2b69 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_down.xml b/app/src/main/res/drawable/ic_keyboard_arrow_down.xml new file mode 100644 index 0000000000000000000000000000000000000000..5c77dd811a876a83089312e7eb312ca24273ffaf --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..68ff39626d7cbb46ad8f3c91e2fe127bb2c5e78b --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/widget_background.xml b/app/src/main/res/drawable/widget_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..d1d23ba6da023008f6222642b1832a83f05c665a --- /dev/null +++ b/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/e_drive_widget.xml b/app/src/main/res/layout/e_drive_widget.xml new file mode 100644 index 0000000000000000000000000000000000000000..3feffb91bf049650fdaa426e5ea73d0725eeac58 --- /dev/null +++ b/app/src/main/res/layout/e_drive_widget.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + +