Bug 1351104 part 2 - Filter the clients list in Send Tab with the FxA device list. r=Grisha

MozReview-Commit-ID: KqSyWVfwva3

--HG--
extra : rebase_source : 49b3eadec9527bc51f99747bc9f5a2bc9f24822a
This commit is contained in:
Edouard Oger 2017-06-07 16:24:30 -04:00
Родитель 322ba44ea6
Коммит 5fccf18ee5
8 изменённых файлов: 141 добавлений и 307 удалений

Просмотреть файл

@ -74,41 +74,6 @@ public class LocalTabsAccessor implements TabsAccessor {
clientsNoStaleSortedUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Clients.CONTENT_NO_STALE_SORTED_URI);
}
/**
* Extracts a List of just RemoteClients from a cursor.
* The supplied cursor should be grouped by guid and sorted by name alphabetically.
*/
@Override
public List<RemoteClient> getClientsWithoutTabsNoStaleSortedFromCursor(Cursor cursor) {
final ArrayList<RemoteClient> clients = new ArrayList<>(cursor.getCount());
final int originalPosition = cursor.getPosition();
try {
if (!cursor.moveToFirst()) {
return clients;
}
final int clientGuidIndex = cursor.getColumnIndex(BrowserContract.Clients.GUID);
final int clientNameIndex = cursor.getColumnIndex(BrowserContract.Clients.NAME);
final int clientLastModifiedIndex = cursor.getColumnIndex(BrowserContract.Clients.LAST_MODIFIED);
final int clientDeviceTypeIndex = cursor.getColumnIndex(BrowserContract.Clients.DEVICE_TYPE);
while (!cursor.isAfterLast()) {
final String clientGuid = cursor.getString(clientGuidIndex);
final String clientName = cursor.getString(clientNameIndex);
final String deviceType = cursor.getString(clientDeviceTypeIndex);
final long lastModified = cursor.getLong(clientLastModifiedIndex);
clients.add(new RemoteClient(clientGuid, clientName, lastModified, deviceType));
cursor.moveToNext();
}
} finally {
cursor.moveToPosition(originalPosition);
}
return clients;
}
/**
* Extract client and tab records from a cursor.
* <p>

Просмотреть файл

@ -20,7 +20,6 @@ public interface TabsAccessor {
public Cursor getRemoteClientsNoStaleSorted(Context context);
public Cursor getRemoteTabsCursor(Context context);
public Cursor getRemoteTabsCursor(Context context, int limit);
public List<RemoteClient> getClientsWithoutTabsNoStaleSortedFromCursor(final Cursor cursor);
public List<RemoteClient> getClientsFromCursor(final Cursor cursor);
public void getTabs(final Context context, final OnQueryTabsCompleteListener listener);
public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener);

Просмотреть файл

@ -9,16 +9,12 @@ import android.accounts.AccountManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.TabsAccessor;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.State;
@ -28,7 +24,11 @@ import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.CommandRunner;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -238,16 +238,19 @@ public class SendTab extends ShareMethod {
return Collections.emptyList();
}
final BrowserDB browserDB = BrowserDB.from(context);
final TabsAccessor tabsAccessor = browserDB.getTabsAccessor();
final Cursor remoteTabsCursor = tabsAccessor.getRemoteClientsNoStaleSorted(context);
final ClientsDatabaseAccessor clientsDatabaseAccessor = new ClientsDatabaseAccessor(context);
try {
if (remoteTabsCursor.getCount() == 0) {
return Collections.emptyList();
final String[] remoteDevicesIds = clientsDatabaseAccessor.getRemoteDevicesIds(context);
final Collection<ClientRecord> clientRecords = clientsDatabaseAccessor.fetchNonStaleClients(remoteDevicesIds);
final Collection<RemoteClient> remoteClients = new ArrayList<>(clientRecords.size());
for (ClientRecord cr : clientRecords) {
remoteClients.add(new RemoteClient(cr.guid, cr.name, cr.lastModified, cr.type));
}
return tabsAccessor.getClientsWithoutTabsNoStaleSortedFromCursor(remoteTabsCursor);
return remoteClients;
} catch (NullCursorException e) {
return Collections.emptyList();
} finally {
remoteTabsCursor.close();
clientsDatabaseAccessor.close();
}
}

Просмотреть файл

@ -24,8 +24,6 @@ package org.mozilla.gecko.widget;
import android.accounts.Account;
import android.content.pm.PackageManager;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.TabsAccessor;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.fxa.FirefoxAccounts;
@ -40,7 +38,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.DataSetObservable;
import android.os.AsyncTask;
import android.text.TextUtils;
@ -52,6 +49,9 @@ import android.util.Xml;
*/
//import com.android.internal.content.PackageMonitor;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@ -62,6 +62,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@ -1314,18 +1315,14 @@ public class ActivityChooserModel extends DataSetObservable {
return false;
}
final BrowserDB browserDB = BrowserDB.from(mContext);
final TabsAccessor tabsAccessor = browserDB.getTabsAccessor();
final Cursor remoteClientsCursor = tabsAccessor
.getRemoteClientsNoStaleSorted(mContext);
if (remoteClientsCursor == null) {
return false;
}
final ClientsDatabaseAccessor clientsDatabaseAccessor = new ClientsDatabaseAccessor(mContext.getApplicationContext());
try {
return remoteClientsCursor.getCount() > 0;
final String[] remoteDevicesIds = clientsDatabaseAccessor.getRemoteDevicesIds(mContext);
return clientsDatabaseAccessor.hasNonStaleClients(remoteDevicesIds);
} catch (NullCursorException e) {
return false;
} finally {
remoteClientsCursor.close();
clientsDatabaseAccessor.close();
}
}

Просмотреть файл

@ -249,6 +249,14 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
return queryHelper.safeQuery(db, ".fetchAllClients", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, null, null);
}
public Cursor fetchClientsWithFxADeviceIds(String[] fxaDeviceIds) throws NullCursorException {
String inClause = computeSQLInClause(fxaDeviceIds.length, COL_FXA_DEVICE_ID);
String query = inClause + " OR " + COL_FXA_DEVICE_ID + " IS NULL";
SQLiteDatabase db = this.getCachedReadableDatabase();
return queryHelper.safeQuery(db, ".fetchClientsWithFxADeviceIds", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, query, fxaDeviceIds);
}
public Cursor fetchAllCommands() throws NullCursorException {
SQLiteDatabase db = this.getCachedReadableDatabase();
@ -261,4 +269,19 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
SQLiteDatabase db = this.getCachedWritableDatabase();
db.delete(TBL_CLIENTS, TBL_CLIENTS_KEY, args);
}
// Pulled from DBUtils
private static String computeSQLInClause(int items, String field) {
final StringBuilder builder = new StringBuilder(field);
builder.append(" IN (");
int i = 0;
for (; i < items - 1; ++i) {
builder.append("?, ");
}
if (i < items) {
builder.append("?");
}
builder.append(")");
return builder.toString();
}
}

Просмотреть файл

@ -8,16 +8,21 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json.simple.JSONArray;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.fxa.devices.FxAccountDevice;
import org.mozilla.gecko.sync.CommandProcessor.Command;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.setup.Constants;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
@ -79,6 +84,58 @@ public class ClientsDatabaseAccessor {
}
}
// Filters our list of clients with the device list we have from FxA.
public Collection<ClientRecord> fetchNonStaleClients(String[] fxaDeviceIds) throws NullCursorException {
final Cursor cur = db.fetchClientsWithFxADeviceIds(fxaDeviceIds);
final Collection<ClientRecord> clients = new ArrayList<>(cur.getCount());
try {
if (!cur.moveToFirst()) {
return clients;
}
while (!cur.isAfterLast()) {
ClientRecord clientRecord = recordFromCursor(cur);
clients.add(clientRecord);
cur.moveToNext();
}
return clients;
} finally {
cur.close();
}
}
public boolean hasNonStaleClients(String[] fxaDeviceIds) throws NullCursorException {
try {
final Cursor cur = db.fetchClientsWithFxADeviceIds(fxaDeviceIds);
try {
return cur.getCount() > 0;
} finally {
cur.close();
}
} catch (NullCursorException e) {
return false;
}
}
public String[] getRemoteDevicesIds(Context context) {
final ContentResolver cr = context.getContentResolver();
final String[] guidProjection = new String[] {
BrowserContract.RemoteDevices.GUID, // 0
};
final Cursor c = cr.query(BrowserContract.RemoteDevices.CONTENT_URI, guidProjection, null, null, "NAME ASC");
final String[] remoteDevicesIds = new String[c.getCount()];
try {
int i = 0;
while (c.moveToNext()) {
remoteDevicesIds[i] = c.getString(c.getColumnIndexOrThrow(BrowserContract.RemoteDevices.GUID));
i++;
}
} finally {
c.close();
}
return remoteDevicesIds;
}
public List<Command> fetchAllCommands() throws NullCursorException {
final List<Command> commands = new ArrayList<Command>();
final Cursor cur = db.fetchAllCommands();

Просмотреть файл

@ -4,6 +4,7 @@
package org.mozilla.gecko.background.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -125,4 +126,40 @@ public class TestClientsDatabaseAccessor extends AndroidTestCase {
}
assertTrue(thrown);
}
public void testFetchNonStaleClients() throws NullCursorException {
String goodRecord1 = Utils.generateGuid();
ClientRecord record1 = new ClientRecord(goodRecord1);
record1.fxaDeviceId = "fxa1";
ClientRecord record2 = new ClientRecord(Utils.generateGuid());
record2.fxaDeviceId = "fxa2";
String goodRecord2 = Utils.generateGuid();
ClientRecord record3 = new ClientRecord(goodRecord2);
record3.fxaDeviceId = "fxa4";
ArrayList<ClientRecord> list = new ArrayList<>();
list.add(record1);
list.add(record2);
list.add(record3);
db.store(list);
assertTrue(db.hasNonStaleClients(new String[]{"fxa1", "fxa-unknown"}));
assertFalse(db.hasNonStaleClients(new String[]{}));
String noFxADeviceId = Utils.generateGuid();
ClientRecord record4 = new ClientRecord(noFxADeviceId);
record4.fxaDeviceId = null;
list.clear();
list.add(record4);
db.store(list);
assertTrue(db.hasNonStaleClients(new String[]{}));
Collection<ClientRecord> filtered = db.fetchNonStaleClients(new String[]{"fxa1", "fxa4", "fxa-unknown"});
ClientRecord[] filteredArr = filtered.toArray(new ClientRecord[0]);
assertEquals(3, filteredArr.length);
assertEquals(filteredArr[0].guid, goodRecord1);
assertEquals(filteredArr[1].guid, goodRecord2);
assertEquals(filteredArr[2].guid, noFxADeviceId);
}
}

Просмотреть файл

@ -1,247 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.db;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.LocalTabsAccessor;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.TabsProvider;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.internal.runtime.RuntimeAdapter;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.List;
@RunWith(TestRunner.class)
public class TestTabsProviderRemoteTabs {
private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
private static final long ONE_WEEK_IN_MILLISECONDS = 7 * ONE_DAY_IN_MILLISECONDS;
private static final long THREE_WEEKS_IN_MILLISECONDS = 3 * ONE_WEEK_IN_MILLISECONDS;
private static final String CLIENT_LOCAL_NAME = "our local";
private static final String CLIENT_REMOTE1_NAME = "The remote1";
private static final String CLIENT_REMOTE2_NAME = "another remote2";
protected TabsProvider provider;
@Before
public void setUp() {
provider = new TabsProvider();
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
}
@After
public void tearDown() throws Exception {
provider.shutdown();
provider = null;
}
protected ContentProviderClient getClientsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
}
@Test
public void testGetClientsWithoutTabsNoStaleSortedFromCursor() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient cpc = getClientsClient();
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
try {
// Delete all tabs to begin with.
cpc.delete(uri, null, null);
Cursor allClients = cpc.query(uri, null, null, null, null);
try {
Assert.assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
// Insert a local and remote1 client record, neither with tabs.
final long now = System.currentTimeMillis();
// Local client has GUID = null.
final ContentValues local = new ContentValues();
local.put(BrowserContract.Clients.NAME, CLIENT_LOCAL_NAME);
local.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
// Remote clients have GUID != null.
final ContentValues remote1 = new ContentValues();
remote1.put(BrowserContract.Clients.GUID, "guid1");
remote1.put(BrowserContract.Clients.NAME, CLIENT_REMOTE1_NAME);
remote1.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
final ContentValues remote2 = new ContentValues();
remote2.put(BrowserContract.Clients.GUID, "guid2");
remote2.put(BrowserContract.Clients.NAME, CLIENT_REMOTE2_NAME);
remote2.put(BrowserContract.Clients.LAST_MODIFIED, now + 3);
ContentValues[] values = new ContentValues[]{local, remote1, remote2};
int inserted = cpc.bulkInsert(uri, values);
Assert.assertEquals(3, inserted);
allClients = cpc.query(BrowserContract.Clients.CONTENT_NO_STALE_SORTED_URI, null, null, null, null);
try {
CursorDumper.dumpCursor(allClients);
// The local client is not ignored.
Assert.assertEquals(3, allClients.getCount());
final List<RemoteClient> clients = accessor.getClientsWithoutTabsNoStaleSortedFromCursor(allClients);
Assert.assertEquals(3, clients.size());
for (RemoteClient client : clients) {
// Each client should not have any tabs.
Assert.assertNotNull(client.tabs);
Assert.assertEquals(0, client.tabs.size());
}
// Client should sorted by name alphabetically.
Assert.assertEquals(CLIENT_REMOTE2_NAME, clients.get(0).name);
Assert.assertEquals(CLIENT_LOCAL_NAME, clients.get(1).name);
Assert.assertEquals(CLIENT_REMOTE1_NAME, clients.get(2).name);
} finally {
allClients.close();
}
// Now let's add a few tabs to one client. The times are chosen so that one tab's
// last used is not relevant, and the other tab is the most recent used.
final ContentValues remoteTab1 = new ContentValues();
remoteTab1.put(BrowserContract.Tabs.CLIENT_GUID, "guid1");
remoteTab1.put(BrowserContract.Tabs.TITLE, "title1");
remoteTab1.put(BrowserContract.Tabs.URL, "http://test.com/test1");
remoteTab1.put(BrowserContract.Tabs.HISTORY, "[\"http://test.com/test1\"]");
remoteTab1.put(BrowserContract.Tabs.LAST_USED, now);
remoteTab1.put(BrowserContract.Tabs.POSITION, 0);
final ContentValues remoteTab2 = new ContentValues();
remoteTab2.put(BrowserContract.Tabs.CLIENT_GUID, "guid1");
remoteTab2.put(BrowserContract.Tabs.TITLE, "title2");
remoteTab2.put(BrowserContract.Tabs.URL, "http://test.com/test2");
remoteTab2.put(BrowserContract.Tabs.HISTORY, "[\"http://test.com/test2\"]");
remoteTab2.put(BrowserContract.Tabs.LAST_USED, now + 5);
remoteTab2.put(BrowserContract.Tabs.POSITION, 1);
values = new ContentValues[]{remoteTab1, remoteTab2};
inserted = cpc.bulkInsert(BrowserContract.Tabs.CONTENT_URI, values);
Assert.assertEquals(2, inserted);
allClients = cpc.query(BrowserContract.Clients.CONTENT_NO_STALE_SORTED_URI, null, BrowserContract.Clients.GUID + " IS NOT NULL", null, null);
try {
CursorDumper.dumpCursor(allClients);
// The local client is ignored.
Assert.assertEquals(2, allClients.getCount());
final List<RemoteClient> clients = accessor.getClientsWithoutTabsNoStaleSortedFromCursor(allClients);
Assert.assertEquals(2, clients.size());
for (RemoteClient client : clients) {
// Each client should be remote and should not have any tabs.
Assert.assertNotNull(client.guid);
Assert.assertNotNull(client.tabs);
Assert.assertEquals(0, client.tabs.size());
}
// Client should sorted by name alphabetically.
Assert.assertEquals(CLIENT_REMOTE2_NAME, clients.get(0).name);
Assert.assertEquals(CLIENT_REMOTE1_NAME, clients.get(1).name);
} finally {
allClients.close();
}
} finally {
cpc.release();
}
}
@Test
public void testGetRecentRemoteClientsUpToOneWeekOld() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient cpc = getClientsClient();
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
final Context context = RuntimeEnvironment.application.getApplicationContext();
try {
// Start Clean
cpc.delete(uri, null, null);
final Cursor allClients = cpc.query(uri, null, null, null, null);
try {
Assert.assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
// Insert a local and remote1 client record, neither with tabs.
final long now = System.currentTimeMillis();
// Local client has GUID = null.
final ContentValues local = new ContentValues();
local.put(BrowserContract.Clients.NAME, "local");
local.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
// Remote clients have GUID != null.
final ContentValues remote1 = new ContentValues();
remote1.put(BrowserContract.Clients.GUID, "guid1");
remote1.put(BrowserContract.Clients.NAME, "remote1");
remote1.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
// Insert a Remote Client that is 6 days old.
final ContentValues remote2 = new ContentValues();
remote2.put(BrowserContract.Clients.GUID, "guid2");
remote2.put(BrowserContract.Clients.NAME, "remote2");
remote2.put(BrowserContract.Clients.LAST_MODIFIED, now - ONE_WEEK_IN_MILLISECONDS + ONE_DAY_IN_MILLISECONDS);
// Insert a Remote Client with the same name as previous but with more than 3 weeks old
final ContentValues remote3 = new ContentValues();
remote3.put(BrowserContract.Clients.GUID, "guid21");
remote3.put(BrowserContract.Clients.NAME, "remote2");
remote3.put(BrowserContract.Clients.LAST_MODIFIED, now - THREE_WEEKS_IN_MILLISECONDS - ONE_DAY_IN_MILLISECONDS);
// Insert another remote client with the same name as previous but with 3 weeks - 1 day old.
final ContentValues remote4 = new ContentValues();
remote4.put(BrowserContract.Clients.GUID, "guid22");
remote4.put(BrowserContract.Clients.NAME, "remote2");
remote4.put(BrowserContract.Clients.LAST_MODIFIED, now - THREE_WEEKS_IN_MILLISECONDS + ONE_DAY_IN_MILLISECONDS);
// Insert a Remote Client that is exactly one week old.
final ContentValues remote5 = new ContentValues();
remote5.put(BrowserContract.Clients.GUID, "guid3");
remote5.put(BrowserContract.Clients.NAME, "remote3");
remote5.put(BrowserContract.Clients.LAST_MODIFIED, now - ONE_WEEK_IN_MILLISECONDS);
ContentValues[] values = new ContentValues[]{local, remote1, remote2, remote3, remote4, remote5};
int inserted = cpc.bulkInsert(uri, values);
Assert.assertEquals(values.length, inserted);
final Cursor remoteClients =
accessor.getRemoteClientsNoStaleSorted(context);
try {
CursorDumper.dumpCursor(remoteClients);
// Local client is not included.
// (remote1, guid1), (remote2, guid2), (remote3, guid3) are expected.
Assert.assertEquals(3, remoteClients.getCount());
// Check the inner data, according to recency.
List<RemoteClient> recentRemoteClientsList =
accessor.getClientsWithoutTabsNoStaleSortedFromCursor(remoteClients);
Assert.assertEquals(3, recentRemoteClientsList.size());
Assert.assertEquals("remote1", recentRemoteClientsList.get(0).name);
Assert.assertEquals("guid1", recentRemoteClientsList.get(0).guid);
Assert.assertEquals("remote2", recentRemoteClientsList.get(1).name);
Assert.assertEquals("guid2", recentRemoteClientsList.get(1).guid);
Assert.assertEquals("remote3", recentRemoteClientsList.get(2).name);
Assert.assertEquals("guid3", recentRemoteClientsList.get(2).guid);
} finally {
remoteClients.close();
}
} finally {
cpc.release();
}
}
}