From eebe07b748fc0f96b19e75fd40a409efa38e8448 Mon Sep 17 00:00:00 2001 From: TheScarastic Date: Wed, 9 Mar 2022 18:45:13 +0530 Subject: [PATCH] eDrive: Initial widget --- app/build.gradle | 3 + app/src/main/AndroidManifest.xml | 197 +++++++------ .../e/drive/operations/GetAliasOperation.java | 82 +++++ .../foundation/e/drive/utils/CommonUtils.java | 25 ++ .../e/drive/widgets/EDriveWidget.java | 279 ++++++++++++++++++ .../e/drive/widgets/EDriveWidgetCallback.java | 16 + .../main/res/drawable/button_background.xml | 7 + app/src/main/res/drawable/ic_arrow_up.xml | 9 + app/src/main/res/drawable/ic_clipboard.xml | 9 + app/src/main/res/drawable/ic_cloud.xml | 17 ++ .../res/drawable/ic_keyboard_arrow_down.xml | 9 + app/src/main/res/drawable/ic_settings.xml | 10 + .../main/res/drawable/widget_background.xml | 6 + app/src/main/res/layout/e_drive_widget.xml | 162 ++++++++++ .../main/res/layout/e_drive_widget_login.xml | 61 ++++ app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/strings.xml | 18 ++ app/src/main/res/xml/e_drive_widget_info.xml | 9 + gradle.properties | 2 +- nextcloud-android-lib | 2 +- 21 files changed, 836 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java create mode 100644 app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java create mode 100644 app/src/main/java/foundation/e/drive/widgets/EDriveWidgetCallback.java create mode 100644 app/src/main/res/drawable/button_background.xml create mode 100644 app/src/main/res/drawable/ic_arrow_up.xml create mode 100644 app/src/main/res/drawable/ic_clipboard.xml create mode 100644 app/src/main/res/drawable/ic_cloud.xml create mode 100644 app/src/main/res/drawable/ic_keyboard_arrow_down.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/widget_background.xml create mode 100644 app/src/main/res/layout/e_drive_widget.xml create mode 100644 app/src/main/res/layout/e_drive_widget_login.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/xml/e_drive_widget_info.xml diff --git a/app/build.gradle b/app/build.gradle index c65b01d7..6efc99a9 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 be871995..1578dbf9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,95 +1,102 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java b/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java new file mode 100644 index 00000000..4f67f76f --- /dev/null +++ b/app/src/main/java/foundation/e/drive/operations/GetAliasOperation.java @@ -0,0 +1,82 @@ +/* + * 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.operations; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.GetMethod; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; + +/** + * @author TheScarastic + */ + +public class GetAliasOperation extends RemoteOperation { + + private static final String TAG = GetAliasOperation.class.getSimpleName(); + private static final String ALIAS_PATH = "/ocs/v1.php/cloud/hide-my-email/"; + + // JSON Node names + private static final String NODE_OCS = "ocs"; + private static final String NODE_DATA = "data"; + private static final String NODE_ALIASES = "aliases"; + + + private boolean isSuccess(int status) { + return (status == HttpStatus.SC_OK); + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result; + GetMethod get = null; + final String uri = client.getBaseUri() + ALIAS_PATH + client.getCredentials().getUsername(); + + try { + get = new GetMethod(uri); + get.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE); + get.setQueryString(new NameValuePair[]{new NameValuePair("format", "json")}); + + if (isSuccess(client.executeMethod(get))) { + String response = get.getResponseBodyAsString(); + // parse + final JSONArray aliases = new JSONObject(response).getJSONObject(NODE_OCS) + .getJSONObject(NODE_DATA).getJSONArray(NODE_ALIASES); + final ArrayList resultAliases = new ArrayList<>(); + + for (int i = 0; i < aliases.length(); i++) { + resultAliases.add(aliases.get(i)); + } + + result = new RemoteOperationResult(true, get); + result.setData(resultAliases); + } else { + result = new RemoteOperationResult(false, get); + } + + } catch (Exception e) { + e.printStackTrace(); + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Fetching aliases failed"); + } finally { + if (get != null) + get.releaseConnection(); + } + + return result; + } +} 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 e0f762fc..2a66b9dd 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 00000000..58f45418 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java @@ -0,0 +1,279 @@ +/* + * 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.ClipData; +import android.content.ClipboardManager; +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.view.View; +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.ArrayList; +import java.util.Calendar; +import java.util.Locale; + +import foundation.e.drive.R; +import foundation.e.drive.operations.GetAliasOperation; +import foundation.e.drive.utils.CommonUtils; + +/** + * @author TheScarastic + * 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 ADD_ACCOUNT_WEBPAGE = "https://e.foundation/e-email-invite/"; + private static final String ACCOUNT_MANAGER_PACKAGE_NAME = "foundation.e.accountmanager"; + private static final String GET_ACCOUNT_MANAGER_COMPONENT_NAME = ACCOUNT_MANAGER_PACKAGE_NAME + + ".ui.setup.LoginActivity"; + private static final String SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type"; + private static final String ACCOUNT_PROVIDER_EELO = "eelo"; + + private static final String SHOW_ALIAS = "show_alias"; + private static final String HIDE_ALIAS = "hide_alias"; + private static final String COPY_ALIAS = "copy_alias"; + + private static boolean showAlias = false; + + private final Calendar calender = Calendar.getInstance(); + private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); + private final GetRemoteUserInfoOperation getRemoteUserInfoOperation = new GetRemoteUserInfoOperation(); + private final GetAliasOperation getAliasOperation = new GetAliasOperation(); + private ArrayList aliases; + 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); + + final HandlerThread handlerThread = new HandlerThread("Network Request"); + handlerThread.start(); + final 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); + } + RemoteOperationResult aliasResult = getAliasOperation.execute(client); + if (aliasResult.isSuccess() && aliasResult.getData() != null) { + aliases = aliasResult.getData(); + } + } + 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); + } + super.onUpdate(context, appWidgetManager, appWidgetIds); + } + + @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; + switch (action) { + case Intent.ACTION_BOOT_COMPLETED: + case AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION: + updateAppWidget(context); + break; + case SHOW_ALIAS: + showAlias = true; + updateAppWidget(context); + break; + case HIDE_ALIAS: + showAlias = false; + updateAppWidget(context); + break; + case COPY_ALIAS: + copyToClipboard(intent.getData(), context); + break; + } + } + + private String dataForWeb(Long bytes) { + final String space = CommonUtils.humanReadableByteCountBin(bytes); + final String[] split = space.split(" "); + return Math.round(Double.parseDouble(split[0])) + split[1]; + } + + private Intent buildIntent(String name, String extra) { + final 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(ACCOUNT_MANAGER_PACKAGE_NAME, + GET_ACCOUNT_MANAGER_COMPONENT_NAME)) + .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, ADD_ACCOUNT_WEBPAGE), PendingIntent.FLAG_IMMUTABLE); + views.setOnClickPendingIntent(R.id.newAccount, pendingIntentNewAccount); + appWidgetManager.updateAppWidget(appWidgetId, views); + return; + } + + // Construct the RemoteViews object + final int usedMB = convertIntoMB(userInfo.quota.used); + final int totalMB = convertIntoMB(userInfo.quota.total); + + 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, totalMB, 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()))); + + if (aliases.isEmpty()) { + views.setViewVisibility(R.id.show_alias, View.GONE); + views.setViewVisibility(R.id.alias1_container, View.GONE); + views.setViewVisibility(R.id.hide_alias, View.GONE); + } else { + views.setTextViewText(R.id.alias1, context.getString(R.string.alias_dot) + aliases.get(0)); + views.setOnClickPendingIntent(R.id.show_alias, getPendingSelfIntent(context, SHOW_ALIAS, + null)); + views.setOnClickPendingIntent(R.id.hide_alias, getPendingSelfIntent(context, HIDE_ALIAS, + null)); + views.setOnClickPendingIntent(R.id.alias1_clipboard, getPendingSelfIntent(context, COPY_ALIAS, + String.valueOf(aliases.get(0)))); + } + + if (showAlias) { + views.setViewVisibility(R.id.show_alias, View.GONE); + views.setViewVisibility(R.id.hide_alias, View.VISIBLE); + views.setViewVisibility(R.id.alias1_container, View.VISIBLE); + } else { + views.setViewVisibility(R.id.show_alias, View.VISIBLE); + views.setViewVisibility(R.id.hide_alias, View.GONE); + views.setViewVisibility(R.id.alias1_container, View.GONE); + } + + final PendingIntent pendingIntentSettings = PendingIntent.getActivity(context, 0, + buildIntent(Settings.ACTION_SYNC_SETTINGS, ""), PendingIntent.FLAG_IMMUTABLE); + views.setOnClickPendingIntent(R.id.settings, pendingIntentSettings); + + final 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); + } + + private PendingIntent getPendingSelfIntent(Context context, String action, String data) { + final Intent intent = new Intent(context, getClass()); + intent.setAction(action); + if (data != null) { + intent.setData(Uri.parse(data)); + } + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); + } + + private void copyToClipboard(Uri data, Context context) { + final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + final ClipData clip = ClipData.newPlainText("alias", String.valueOf(data)); + clipboard.setPrimaryClip(clip); + } + + private int convertIntoMB(Long quota) { + return (int) (quota / 1048576); // 1024.0 * 1024.0 = 1048576.0 + } + +} \ 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 00000000..1cab2e23 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/widgets/EDriveWidgetCallback.java @@ -0,0 +1,16 @@ +/* + * 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; + +/** + * @author TheScarastic + */ +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 00000000..1c1a64ee --- /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_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 00000000..fa9dfe41 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_clipboard.xml b/app/src/main/res/drawable/ic_clipboard.xml new file mode 100644 index 00000000..bce88a03 --- /dev/null +++ b/app/src/main/res/drawable/ic_clipboard.xml @@ -0,0 +1,9 @@ + + + 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 00000000..3bffa014 --- /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 00000000..5c77dd81 --- /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 00000000..68ff3962 --- /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 00000000..d1d23ba6 --- /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 00000000..8be1a18d --- /dev/null +++ b/app/src/main/res/layout/e_drive_widget.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +