Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b86fe490 authored by Abhishek Aggarwal's avatar Abhishek Aggarwal
Browse files

Merge branch 'epic-5-ecloud-integration' into 'v1-oreo'

eDrive: Initial widget

See merge request !101
parents 1e3e6bdb eebe07b7
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -53,6 +53,9 @@ android {
            //includeAndroidResources = true
        }
    }
    buildFeatures {
        viewBinding true
    }

}

+102 −95
Original line number Diff line number Diff line
<!--
Copyright © Vincent Bourgmayer (/e/ foundation).
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
-->
<!-- @author Vincent Bourgmayer -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="foundation.e.drive">

@@ -15,8 +8,10 @@ http://www.gnu.org/licenses/gpl.html
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- needed for PersistedJob -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

    <permission
        android:name="android.permission.FORCE_STOP_PACKAGES"
@@ -30,6 +25,20 @@ http://www.gnu.org/licenses/gpl.html
        android:icon="@mipmap/ic_eelo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_eelo_round">
        <receiver
            android:name=".widgets.EDriveWidget"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED"/>
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/e_drive_widget_info" />
        </receiver>

        <!-- Providers -->
        <provider
            android:name=".providers.MediasSyncProvider"
@@ -42,9 +51,7 @@ http://www.gnu.org/licenses/gpl.html
            android:authorities="foundation.e.drive.providers.SettingsSyncProvider"
            android:enabled="true"
            android:exported="true"
            android:label="Application settings" />

        <!-- Services -->
            android:label="Application settings" /> <!-- Services -->
        <service
            android:name=".services.InitializerService"
            android:enabled="true"
+82 −0
Original line number Diff line number Diff line
/*
 * 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<Object> 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;
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -32,6 +32,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
+279 −0
Original line number Diff line number Diff line
/*
 * 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&current-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<Object> 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
Loading