Преглед на файлове

Preparation for account import

Signed-off-by: Mario Danic <mario@lovelyhq.com>
Mario Danic преди 7 години
родител
ревизия
ec9451caae

+ 6 - 0
app/src/main/AndroidManifest.xml

@@ -14,11 +14,17 @@
 
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission
+        android:name="android.permission.GET_ACCOUNTS"
+        android:maxSdkVersion="22"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission
+        android:name="android.permission.USE_CREDENTIALS"
+        android:maxSdkVersion="22"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
 

+ 10 - 1
app/src/main/java/com/nextcloud/talk/adapters/items/AdvancedUserItem.java

@@ -20,6 +20,8 @@
 
 package com.nextcloud.talk.adapters.items;
 
+import android.accounts.Account;
+import android.support.annotation.Nullable;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -52,10 +54,12 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
 
     private Participant participant;
     private UserEntity userEntity;
+    @Nullable private Account account;
 
-    public AdvancedUserItem(Participant participant, UserEntity userEntity) {
+    public AdvancedUserItem(Participant participant, UserEntity userEntity, @Nullable Account account) {
         this.participant = participant;
         this.userEntity = userEntity;
+        this.account = account;
     }
 
     @Override
@@ -84,6 +88,11 @@ public class AdvancedUserItem extends AbstractFlexibleItem<AdvancedUserItem.User
         return userEntity;
     }
 
+    @Nullable
+    public Account getAccount() {
+        return account;
+    }
+
     @Override
     public int getLayoutRes() {
         return R.layout.rv_item_call;

+ 40 - 7
app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.java

@@ -23,6 +23,7 @@ package com.nextcloud.talk.controllers;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.net.Uri;
+import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.text.Editable;
 import android.text.TextUtils;
@@ -41,7 +42,9 @@ import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.api.helpers.api.ApiHelper;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
+import com.nextcloud.talk.utils.AccountUtils;
 import com.nextcloud.talk.utils.ErrorMessageHolder;
+import com.nextcloud.talk.utils.database.user.UserUtils;
 
 import java.security.cert.CertificateException;
 
@@ -66,12 +69,15 @@ public class ServerSelectionController extends BaseController {
     TextFieldBoxes textFieldBoxes;
     @BindView(R.id.progress_bar)
     ProgressBar progressBar;
-    @BindView(R.id.providers_text_view)
+    @BindView(R.id.helper_text_view)
     TextView providersTextView;
 
     @Inject
     NcApi ncApi;
 
+    @Inject
+    UserUtils userUtils;
+
     private Disposable statusQueryDisposable;
 
     @Override
@@ -99,14 +105,41 @@ public class ServerSelectionController extends BaseController {
         textFieldBoxes.getEndIconImageButton().setVisibility(View.VISIBLE);
         textFieldBoxes.getEndIconImageButton().setOnClickListener(view1 -> checkServerAndProceed());
 
-        if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url))) {
+        if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url)) && (TextUtils.isEmpty(getResources
+                ().getString(R.string.nc_import_account_type)))) {
             providersTextView.setVisibility(View.GONE);
         } else {
-            providersTextView.setOnClickListener(view12 -> {
-                Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getResources()
-                        .getString(R.string.nc_providers_url)));
-                startActivity(browserIntent);
-            });
+            if ((TextUtils.isEmpty(getResources
+                    ().getString(R.string.nc_import_account_type)) || AccountUtils.findAccounts().size() == 0) &&
+                    userUtils.getUsers().size() == 0) {
+
+                providersTextView.setText(R.string.nc_get_from_provider);
+                providersTextView.setOnClickListener(view12 -> {
+                    Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getResources()
+                            .getString(R.string.nc_providers_url)));
+                    startActivity(browserIntent);
+                });
+            } else if (AccountUtils.findAccounts().size() > 0) {
+                if (!TextUtils.isEmpty(AccountUtils.getAppNameBasedOnPackage(getResources()
+                                .getString(R.string.nc_import_accounts_from)))) {
+                    providersTextView.setText(String.format(getResources().getString(R.string
+                            .nc_server_import_accounts), AccountUtils.getAppNameBasedOnPackage(getResources()
+                            .getString(R.string.nc_import_accounts_from))));
+                } else {
+                    providersTextView.setText(getResources().getString(R.string.nc_server_import_accounts_plain_plural));
+                }
+
+                providersTextView.setOnClickListener(view13 -> {
+                    Bundle bundle = new Bundle();
+                    bundle.putBoolean("isAccountImport", true);
+                    getRouter().pushController(RouterTransaction.with(
+                            new SwitchAccountController(bundle))
+                            .pushChangeHandler(new HorizontalChangeHandler())
+                            .popChangeHandler(new HorizontalChangeHandler()));
+                });
+            } else {
+                providersTextView.setVisibility(View.GONE);
+            }
         }
 
         serverEntry.requestFocus();

+ 110 - 14
app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.java

@@ -16,15 +16,27 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
+ * https://github.com/nextcloud/ownCloud-Account-Importer
  */
 
 package com.nextcloud.talk.controllers;
 
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.OperationCanceledException;
+import android.os.Bundle;
+import android.os.Handler;
 import android.support.annotation.NonNull;
 import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.DividerItemDecoration;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,7 +46,9 @@ import com.nextcloud.talk.adapters.items.AdvancedUserItem;
 import com.nextcloud.talk.api.models.json.participants.Participant;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
+import com.nextcloud.talk.models.ImportAccount;
 import com.nextcloud.talk.persistence.entities.UserEntity;
+import com.nextcloud.talk.utils.AccountUtils;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 
 import java.net.CookieManager;
@@ -54,6 +68,7 @@ import io.reactivex.disposables.Disposable;
 @AutoInjector(NextcloudTalkApplication.class)
 public class SwitchAccountController extends BaseController {
 
+    private static final String TAG = "SwitchAccountController";
     @Inject
     UserUtils userUtils;
 
@@ -68,7 +83,21 @@ public class SwitchAccountController extends BaseController {
     private FlexibleAdapter<AbstractFlexibleItem> adapter;
     private List<AbstractFlexibleItem> userItems = new ArrayList<>();
 
-    private FlexibleAdapter.OnItemClickListener onItemClickListener =
+    private boolean isAccountImport = false;
+
+    private FlexibleAdapter.OnItemClickListener onImportItemClickListener = new FlexibleAdapter.OnItemClickListener() {
+        @Override
+        public boolean onItemClick(int position) {
+            if (userItems.size() > position) {
+                Account account = ((AdvancedUserItem) userItems.get(position)).getAccount();
+                getAuthTokenForAccount(account);
+            }
+
+            return true;
+        }
+    };
+
+    private FlexibleAdapter.OnItemClickListener onSwitchItemClickListener =
             new FlexibleAdapter.OnItemClickListener() {
                 @Override
                 public boolean onItemClick(int position) {
@@ -106,6 +135,17 @@ public class SwitchAccountController extends BaseController {
                 }
             };
 
+    public SwitchAccountController() {
+    }
+
+    public SwitchAccountController(Bundle args) {
+        super(args);
+
+        if (args.containsKey("isAccountImport")) {
+            isAccountImport = true;
+        }
+    }
+
     @Override
     protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
         return inflater.inflate(R.layout.controller_generic_rv, container, false);
@@ -123,19 +163,39 @@ public class SwitchAccountController extends BaseController {
             UserEntity userEntity;
             Participant participant;
 
-            for (Object userEntityObject : userUtils.getUsers()) {
-                userEntity = (UserEntity) userEntityObject;
-                if (!userEntity.getCurrent()) {
-                    participant = new Participant();
-                    participant.setName(userEntity.getDisplayName());
-                    participant.setUserId(userEntity.getUsername());
-                    userItems.add(new AdvancedUserItem(participant, userEntity));
+            if (!isAccountImport) {
+                for (Object userEntityObject : userUtils.getUsers()) {
+                    userEntity = (UserEntity) userEntityObject;
+                    if (!userEntity.getCurrent()) {
+                        participant = new Participant();
+                        participant.setName(userEntity.getDisplayName());
+                        participant.setUserId(userEntity.getUsername());
+                        userItems.add(new AdvancedUserItem(participant, userEntity, null));
+                    }
                 }
-            }
 
-            adapter.addListener(onItemClickListener);
-            adapter.updateDataSet(userItems, false);
-        }
+                adapter.addListener(onSwitchItemClickListener);
+                adapter.updateDataSet(userItems, false);
+            } else {
+                getActionBar().show();
+                Account account;
+                ImportAccount importAccount;
+                for (Object accountObject : AccountUtils.findAccounts(userUtils.getUsers())) {
+                        account = (Account) accountObject;
+                        importAccount = AccountUtils.getInformationFromAccount(account, null);
+
+                        participant = new Participant();
+                        participant.setName(importAccount.getUsername());
+                        participant.setUserId(importAccount.getUsername());
+                        userEntity = new UserEntity();
+                        userEntity.setBaseUrl(importAccount.getServerUrl());
+                        userItems.add(new AdvancedUserItem(participant, userEntity, account));
+                    }
+                }
+
+                adapter.addListener(onSwitchItemClickListener);
+                adapter.updateDataSet(userItems, false);
+            }
 
         prepareViews();
     }
@@ -154,10 +214,46 @@ public class SwitchAccountController extends BaseController {
         swipeRefreshLayout.setEnabled(false);
     }
 
+    private void getAuthTokenForAccount(Account account) {
+        final AccountManager accMgr = AccountManager.get(getActivity());
+
+        final AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
+                .setTitle(getResources().getString(R.string.nc_server_import_accounts_plain_singular))
+                .setMessage(getResources().getString(R.string.nc_server_import_account_notification))
+                .create();
+
+        alertDialog.show();
+
+        String authTokenType = getResources().getString(R.string.nc_import_account_type) + ".password";
+
+        final Handler handler = new Handler();
+        accMgr.getAuthToken(account, authTokenType, true,
+                new AccountManagerCallback<Bundle>() {
+
+                    @Override
+                    public void run(AccountManagerFuture<Bundle> future) {
+
+                        try {
+                            ImportAccount importAccount = AccountUtils.getInformationFromAccount(account, future
+                                    .getResult());
+                        } catch (OperationCanceledException e) {
+                            Log.e(TAG, "Access was denied");
+                            // TODO: The user has denied you access to the API, handle this later on
+                        } catch (Exception e) {
+                            Log.e(TAG, "Something went wrong while accessing token");
+                        }
+
+                        alertDialog.dismiss();
+
+                    }
+                }, handler
+
+        );
+
+    }
+
     @Override
     protected String getTitle() {
         return getResources().getString(R.string.nc_select_an_account);
     }
-
-
 }

+ 38 - 0
app/src/main/java/com/nextcloud/talk/models/ImportAccount.java

@@ -0,0 +1,38 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.talk.models;
+
+import android.support.annotation.Nullable;
+
+import lombok.Data;
+
+@Data
+public class ImportAccount {
+    public String username;
+    @Nullable public String token;
+    public String serverUrl;
+
+    public ImportAccount(String username, @Nullable String token, String serverUrl) {
+        this.username = username;
+        this.token = token;
+        this.serverUrl = serverUrl;
+    }
+}

+ 122 - 0
app/src/main/java/com/nextcloud/talk/utils/AccountUtils.java

@@ -0,0 +1,122 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
+ * https://github.com/nextcloud/ownCloud-Account-Importer
+ */
+
+package com.nextcloud.talk.utils;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.application.NextcloudTalkApplication;
+import com.nextcloud.talk.models.ImportAccount;
+import com.nextcloud.talk.persistence.entities.UserEntity;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AccountUtils {
+
+    private static final String TAG = "AccountUtils";
+
+    public static List<Account> findAccounts(List<UserEntity> userEntitiesList) {
+        Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
+        final AccountManager accMgr = AccountManager.get(context);
+        final Account[] accounts = accMgr.getAccounts();
+
+        List<Account> accountsAvailable = new ArrayList<>();
+        ImportAccount importAccount;
+        UserEntity internalUserEntity;
+        boolean accountFound;
+        for (Account account : accounts) {
+            accountFound = false;
+            String accountType = account.type.intern();
+
+            if (context.getResources().getString(R.string.nc_import_account_type).equals(accountType)) {
+                for (int i = 0; i < userEntitiesList.size(); i++) {
+                    internalUserEntity = userEntitiesList.get(i);
+                    importAccount = getInformationFromAccount(account, null);
+                    if (internalUserEntity.getUsername().equals(importAccount.getUsername()) &&
+                            internalUserEntity.getBaseUrl().equals(importAccount.getServerUrl())) {
+                        accountFound = true;
+                        break;
+                    }
+                }
+
+                if (!accountFound) {
+                    accountsAvailable.add(account);
+                }
+            }
+        }
+
+        return accountsAvailable;
+    }
+
+    public static String getAppNameBasedOnPackage(String packageName) {
+        Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
+        PackageManager packageManager = context.getPackageManager();
+        String appName = "";
+        try {
+            appName = (String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Failed to get app name based on package");
+        }
+
+        return appName;
+    }
+
+    public static ImportAccount getInformationFromAccount(Account account, @Nullable Bundle data) {
+        int lastAtPos = account.name.lastIndexOf("@");
+        String urlString = account.name.substring(lastAtPos + 1);
+        String username = account.name.substring(0, lastAtPos);
+
+        if (!urlString.startsWith("http"))
+            urlString = "https://" + urlString;
+
+        String password = null;
+        if (data != null) {
+            password = data.getString(AccountManager.KEY_AUTHTOKEN);
+        }
+
+        try {
+            final String urlStringOrig = urlString;
+            URL url = new URL(urlStringOrig);
+            urlString = url.getProtocol() + "://" + url.getHost();
+            if (url.getPath().contains("/owncloud")) {
+                urlString += url.getPath().substring(0, url.getPath().indexOf("/owncloud") + 9);
+            } else if (url.getPath().contains("/")) {
+                urlString += url.getPath().substring(0, url.getPath().indexOf("/"));
+            }
+        } catch (Exception ex) {
+            Log.e(TAG, "Something went wrong while trying to create url string");
+        }
+
+        return new ImportAccount(username, password, urlString);
+    }
+}
+

+ 1 - 1
app/src/main/res/layout/controller_server_selection.xml

@@ -80,7 +80,7 @@
         android:visibility="invisible"/>
 
     <TextView
-        android:id="@+id/providers_text_view"
+        android:id="@+id/helper_text_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_below="@id/progress_bar"

+ 4 - 0
app/src/main/res/values/setup.xml

@@ -14,4 +14,8 @@
     <string name="nc_gpl3_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.en.html</string>
     <string name="nc_source_code_url" translatable="false">https://github.com/nextcloud/talk-android</string>
     <string name="nc_providers_url" translatable="false">https://nextcloud.com/providers</string>
+
+    <!-- Package name from which to import accounts - if empty, won't ever be shown -->
+    <string name="nc_import_accounts_from" translatable="false">com.nextcloud.client</string>
+    <string name="nc_import_account_type" translatable="false">nextcloud</string>
 </resources>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -11,6 +11,10 @@
     <string name="nc_server_db_upgrade_needed">Please upgrade your %1$s database</string>
     <string name="nc_server_maintenance">Please bring your %1$s out of maintenance</string>
     <string name="nc_server_version">%1$s only works with %2$s 13 and up</string>
+    <string name="nc_server_import_accounts_plain_singular">Import account</string>
+    <string name="nc_server_import_accounts_plain_plural">Import accounts</string>
+    <string name="nc_server_import_accounts">Import accounts from the %1$s app</string>
+    <string name="nc_server_import_account_notification">Please grant access to the selected account in the notification bar!"</string>
     <string name="nc_get_from_provider">Do you not have a server yet?\nClick here to get one from a provider</string>
 
     <!-- Account verification -->