Skip to content

Commit 33dffb6

Browse files
committed
Show dialog when authenticator conflict exists
A SecurityException is raised when multiple apps provide authenticator services for the same account type id. This causes the GitHub app to crash when a another app has already registered an account using this id (com.mobile). The workaround is to catch the exception and show a dialog describing the conflict and how it can be resolved. Closes issue pockethub#72
1 parent ec38793 commit 33dffb6

File tree

7 files changed

+122
-19
lines changed

7 files changed

+122
-19
lines changed

app/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -204,5 +204,7 @@
204204
<string name="message_invalid_github_url">The following URL could not be opened by this application:\n{0}</string>
205205
<string name="recently_viewed">RECENTLY VIEWED</string>
206206
<string name="cancel">Cancel</string>
207+
<string name="authenticator_conflict_title">App Conflict</string>
208+
<string name="authenticator_conflict_message">Another installed app is already configured for GitHub authentication.\n\nYou must remove the other app from the Accounts &amp; sync settings and uninstall it before the GitHub app can be used.</string>
207209

208210
</resources>

app/src/main/java/com/github/mobile/ThrowableLoader.java

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ public ThrowableLoader(Context context, D data) {
4545
this.data = data;
4646
}
4747

48+
@Override
49+
protected D getAccountFailureData() {
50+
return data;
51+
}
52+
53+
@Override
4854
public D load() {
4955
exception = null;
5056
try {

app/src/main/java/com/github/mobile/accounts/AccountScope.java

-12
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import android.accounts.Account;
1919
import android.accounts.AccountManager;
20-
import android.app.Activity;
2120

2221
import com.google.inject.AbstractModule;
2322
import com.google.inject.Key;
@@ -59,17 +58,6 @@ AccountScope.<GitHubAccount> seededKeyProvider()).in(
5958

6059
private final Map<GitHubAccount, Map<Key<?>, Object>> repoScopeMaps = new ConcurrentHashMap<GitHubAccount, Map<Key<?>, Object>>();
6160

62-
/**
63-
* Enters scope once we've ensured the user has a valid account.
64-
*
65-
* @param activity
66-
*/
67-
public void enterWith(final Activity activity) {
68-
AccountManager accountManager = AccountManager.get(activity);
69-
Account account = AccountUtils.getAccount(accountManager, activity);
70-
enterWith(account, accountManager);
71-
}
72-
7361
/**
7462
* Enters scope using a GitHubAccount derived from the supplied account
7563
*

app/src/main/java/com/github/mobile/accounts/AccountUtils.java

+80-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.github.mobile.accounts;
1717

1818
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
19+
import static android.content.DialogInterface.BUTTON_POSITIVE;
1920
import static android.util.Log.DEBUG;
2021
import static com.github.mobile.accounts.AccountConstants.ACCOUNT_TYPE;
2122
import android.accounts.Account;
@@ -25,11 +26,20 @@
2526
import android.accounts.AuthenticatorException;
2627
import android.accounts.OperationCanceledException;
2728
import android.app.Activity;
29+
import android.app.AlertDialog;
2830
import android.content.Context;
31+
import android.content.DialogInterface;
32+
import android.content.DialogInterface.OnCancelListener;
33+
import android.content.DialogInterface.OnClickListener;
2934
import android.os.Bundle;
3035
import android.util.Log;
3136

37+
import com.github.mobile.R.string;
38+
import com.github.mobile.ui.LightAlertDialog;
39+
3240
import java.io.IOException;
41+
import java.util.ArrayList;
42+
import java.util.List;
3343

3444
import org.eclipse.egit.github.core.User;
3545

@@ -40,6 +50,11 @@ public class AccountUtils {
4050

4151
private static final String TAG = "AccountUtils";
4252

53+
private static class AuthenticatorConflictException extends IOException {
54+
55+
private static final long serialVersionUID = 641279204734869183L;
56+
}
57+
4358
/**
4459
* Is the given user the owner of the default account?
4560
*
@@ -87,7 +102,28 @@ private static Account[] getAccounts(final AccountManager manager)
87102
final AccountManagerFuture<Account[]> future = manager
88103
.getAccountsByTypeAndFeatures(ACCOUNT_TYPE, null, null, null);
89104
final Account[] accounts = future.getResult();
90-
return accounts != null ? accounts : new Account[0];
105+
if (accounts != null && accounts.length > 0)
106+
return getPasswordAccessibleAccounts(manager, accounts);
107+
else
108+
return new Account[0];
109+
}
110+
111+
private static Account[] getPasswordAccessibleAccounts(
112+
final AccountManager manager, final Account[] candidates)
113+
throws AuthenticatorConflictException {
114+
final List<Account> accessible = new ArrayList<Account>(
115+
candidates.length);
116+
boolean exceptionThrown = false;
117+
for (Account account : candidates)
118+
try {
119+
manager.getPassword(account);
120+
accessible.add(account);
121+
} catch (SecurityException ignored) {
122+
exceptionThrown = true;
123+
}
124+
if (accessible.isEmpty() && exceptionThrown)
125+
throw new AuthenticatorConflictException();
126+
return accessible.toArray(new Account[accessible.size()]);
91127
}
92128

93129
/**
@@ -96,9 +132,11 @@ private static Account[] getAccounts(final AccountManager manager)
96132
* @param manager
97133
* @param activity
98134
* @return account
135+
* @throws IOException
136+
* @throws AccountsException
99137
*/
100138
public static Account getAccount(final AccountManager manager,
101-
final Activity activity) {
139+
final Activity activity) throws IOException, AccountsException {
102140
final boolean loggable = Log.isLoggable(TAG, DEBUG);
103141
if (loggable)
104142
Log.d(TAG, "Getting account");
@@ -123,18 +161,55 @@ public static Account getAccount(final AccountManager manager,
123161
} catch (OperationCanceledException e) {
124162
Log.d(TAG, "Excepting retrieving account", e);
125163
activity.finish();
126-
throw new RuntimeException(e);
164+
throw e;
127165
} catch (AccountsException e) {
128166
Log.d(TAG, "Excepting retrieving account", e);
129-
throw new RuntimeException(e);
167+
throw e;
168+
} catch (AuthenticatorConflictException e) {
169+
activity.runOnUiThread(new Runnable() {
170+
171+
public void run() {
172+
showConflictMessage(activity);
173+
}
174+
});
175+
throw e;
130176
} catch (IOException e) {
131177
Log.d(TAG, "Excepting retrieving account", e);
132-
throw new RuntimeException(e);
178+
throw e;
133179
}
134180

135181
if (loggable)
136182
Log.d(TAG, "Returning account " + accounts[0].name);
137183

138184
return accounts[0];
139185
}
186+
187+
/**
188+
* Show conflict message about previously registered authenticator from
189+
* another application
190+
*
191+
* @param activity
192+
*/
193+
private static void showConflictMessage(final Activity activity) {
194+
AlertDialog dialog = LightAlertDialog.create(activity);
195+
dialog.setTitle(activity.getString(string.authenticator_conflict_title));
196+
dialog.setMessage(activity
197+
.getString(string.authenticator_conflict_message));
198+
dialog.setOnCancelListener(new OnCancelListener() {
199+
200+
@Override
201+
public void onCancel(DialogInterface dialog) {
202+
activity.finish();
203+
}
204+
});
205+
dialog.setButton(BUTTON_POSITIVE,
206+
activity.getString(android.R.string.ok), new OnClickListener() {
207+
208+
@Override
209+
public void onClick(DialogInterface dialog, int which) {
210+
activity.finish();
211+
}
212+
});
213+
dialog.show();
214+
}
140215
}

app/src/main/java/com/github/mobile/accounts/AuthenticatedUserLoader.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
*/
1616
package com.github.mobile.accounts;
1717

18+
import android.accounts.Account;
19+
import android.accounts.AccountManager;
20+
import android.accounts.AccountsException;
1821
import android.app.Activity;
1922
import android.content.Context;
2023

2124
import com.github.mobile.AsyncLoader;
2225
import com.google.inject.Inject;
2326

27+
import java.io.IOException;
28+
2429
import roboguice.RoboGuice;
2530
import roboguice.inject.ContextScope;
2631

@@ -55,9 +60,26 @@ public AuthenticatedUserLoader(final Context context) {
5560
RoboGuice.injectMembers(context, this);
5661
}
5762

63+
/**
64+
* Get data to display when obtaining an account fails
65+
*
66+
* @return data
67+
*/
68+
protected abstract D getAccountFailureData();
69+
5870
@Override
5971
public final D loadInBackground() {
60-
accountScope.enterWith(activity);
72+
final AccountManager manager = AccountManager.get(activity);
73+
final Account account;
74+
try {
75+
account = AccountUtils.getAccount(manager, activity);
76+
} catch (IOException e) {
77+
return getAccountFailureData();
78+
} catch (AccountsException e) {
79+
return getAccountFailureData();
80+
}
81+
82+
accountScope.enterWith(account, manager);
6183
try {
6284
contextScope.enter(getContext());
6385
try {

app/src/main/java/com/github/mobile/accounts/AuthenticatedUserTask.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.github.mobile.accounts;
1717

18+
import android.accounts.Account;
19+
import android.accounts.AccountManager;
1820
import android.app.Activity;
1921
import android.content.Context;
2022

@@ -66,7 +68,10 @@ public AuthenticatedUserTask(final Context context, final Executor executor) {
6668

6769
@Override
6870
public final ResultT call() throws Exception {
69-
accountScope.enterWith(activity);
71+
final AccountManager manager = AccountManager.get(activity);
72+
final Account account = AccountUtils.getAccount(manager, activity);
73+
74+
accountScope.enterWith(account, manager);
7075
try {
7176
contextScope.enter(getContext());
7277
try {

app/src/main/java/com/github/mobile/ui/repo/OrganizationLoader.java

+5
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ public OrganizationLoader(Activity activity,
6060
this.userComparatorProvider = userComparatorProvider;
6161
}
6262

63+
@Override
64+
protected List<User> getAccountFailureData() {
65+
return Collections.emptyList();
66+
}
67+
6368
@Override
6469
public List<User> load() {
6570
List<User> orgs;

0 commit comments

Comments
 (0)