From b152e6dd1b134188a9442214a8b4d73f363377cc Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 24 Sep 2018 12:14:27 -0700 Subject: [PATCH 1/7] Update "Previous connected device" preference 1. Show up to 3 devices in main page 2. Add "See all" preference to go ot detail page Bug: 116350449 Test: Screenshot && RunSettingsRoboTests Change-Id: Iee0de8a2b7f2543e946a117ba2d9ca9dde6c8678 --- res/values/strings.xml | 3 ++ res/xml/connected_devices.xml | 16 +++++-- .../bluetooth/BluetoothDeviceUpdater.java | 3 +- .../SavedBluetoothDeviceUpdater.java | 3 +- ...lyConnectedDevicePreferenceController.java | 32 +++++++------ ...nnectedDevicePreferenceControllerTest.java | 47 ++++++++++++++----- 6 files changed, 69 insertions(+), 35 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 7308de74d8d..a99c9d77802 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -414,6 +414,9 @@ Bluetooth turned on + + See all + Date & time diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 424ca750f9b..29c9e01183f 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -41,18 +41,24 @@ settings:useAdminDisabledSummary="true" settings:controller="com.android.settings.connecteddevice.AddDevicePreferenceController"/> - + settings:controller="com.android.settings.connecteddevice.PreviouslyConnectedDevicePreferenceController"> + + + diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 297c90b2904..4cc329cd356 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -269,7 +269,8 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, final BluetoothDevice device = cachedDevice.getDevice(); if (DBG) { Log.d(TAG, "isDeviceConnected() device name : " + cachedDevice.getName() + - ", is connected : " + device.isConnected()); + ", is connected : " + device.isConnected() + " , is profile connected : " + + cachedDevice.isConnected()); } return device.getBondState() == BluetoothDevice.BOND_BONDED && device.isConnected(); } diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java index 8c6d3f26704..6d034fda96f 100644 --- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java @@ -43,7 +43,8 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater final BluetoothDevice device = cachedDevice.getDevice(); if (DBG) { Log.d(TAG, "isFilterMatched() device name : " + cachedDevice.getName() + - ", is connected : " + device.isConnected()); + ", is connected : " + device.isConnected() + ", is profile connected : " + + cachedDevice.isConnected()); } return device.getBondState() == BluetoothDevice.BOND_BONDED && !device.isConnected(); } diff --git a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java index 5f6ffb24eec..ffcd1fc4faa 100644 --- a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java +++ b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java @@ -20,6 +20,7 @@ import android.content.pm.PackageManager; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; import com.android.settings.bluetooth.BluetoothDeviceUpdater; @@ -35,7 +36,9 @@ import com.android.settingslib.core.lifecycle.events.OnStop; public class PreviouslyConnectedDevicePreferenceController extends BasePreferenceController implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback { - private Preference mPreference; + private static final int MAX_DEVICE_NUM = 3; + + private PreferenceGroup mPreferenceGroup; private BluetoothDeviceUpdater mBluetoothDeviceUpdater; private DockUpdater mSavedDockUpdater; private int mPreferenceSize; @@ -57,8 +60,10 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); + mPreferenceGroup = (PreferenceGroup) screen.findPreference(getPreferenceKey()); + mPreferenceGroup.setVisible(false); + if (isAvailable()) { - mPreference = screen.findPreference(getPreferenceKey()); final Context context = screen.getContext(); mBluetoothDeviceUpdater.setPrefContext(context); mSavedDockUpdater.setPreferenceContext(context); @@ -69,7 +74,6 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc public void onStart() { mBluetoothDeviceUpdater.registerCallback(); mSavedDockUpdater.registerCallback(); - updatePreferenceOnSizeChanged(); } @Override @@ -86,13 +90,17 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc @Override public void onDeviceAdded(Preference preference) { mPreferenceSize++; - updatePreferenceOnSizeChanged(); + if (mPreferenceSize <= MAX_DEVICE_NUM) { + mPreferenceGroup.addPreference(preference); + } + updatePreferenceVisiblity(); } @Override public void onDeviceRemoved(Preference preference) { mPreferenceSize--; - updatePreferenceOnSizeChanged(); + mPreferenceGroup.removePreference(preference); + updatePreferenceVisiblity(); } @VisibleForTesting @@ -106,18 +114,12 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc } @VisibleForTesting - void setPreferenceSize(int size) { - mPreferenceSize = size; + void setPreferenceGroup(PreferenceGroup preferenceGroup) { + mPreferenceGroup = preferenceGroup; } @VisibleForTesting - void setPreference(Preference preference) { - mPreference = preference; - } - - private void updatePreferenceOnSizeChanged() { - if (isAvailable()) { - mPreference.setEnabled(mPreferenceSize != 0); - } + void updatePreferenceVisiblity() { + mPreferenceGroup.setVisible(mPreferenceSize > 0); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java index fc08016f4d0..52b2e95593c 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java @@ -28,6 +28,9 @@ import android.content.Context; import android.content.pm.PackageManager; import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceManager; import com.android.settings.bluetooth.BluetoothDeviceUpdater; import com.android.settings.connecteddevice.dock.DockUpdater; @@ -54,10 +57,12 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { private DockUpdater mDockUpdater; @Mock private PackageManager mPackageManager; + @Mock + private PreferenceManager mPreferenceManager; private Context mContext; private PreviouslyConnectedDevicePreferenceController mPreConnectedDeviceController; - private Preference mPreference; + private PreferenceGroup mPreferenceGroup; @Before public void setUp() { @@ -70,8 +75,10 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { mPreConnectedDeviceController.setBluetoothDeviceUpdater(mBluetoothDeviceUpdater); mPreConnectedDeviceController.setSavedDockUpdater(mDockUpdater); - mPreference = new Preference(mContext); - mPreConnectedDeviceController.setPreference(mPreference); + mPreferenceGroup = spy(new PreferenceCategory(mContext)); + doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager(); + mPreferenceGroup.setVisible(false); + mPreConnectedDeviceController.setPreferenceGroup(mPreferenceGroup); } @Test @@ -101,20 +108,34 @@ public class PreviouslyConnectedDevicePreferenceControllerTest { } @Test - public void onDeviceAdded_addFirstDevice_preferenceIsEnable() { - doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); - mPreConnectedDeviceController.setPreferenceSize(0); - mPreConnectedDeviceController.onDeviceAdded(mPreference); + public void onDeviceAdded_addDevicePreference_displayIt() { + mPreConnectedDeviceController.onDeviceAdded(new Preference(mContext)); - assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreferenceGroup.isVisible()).isTrue(); + assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1); } @Test - public void onDeviceRemoved_removeLastDevice_preferenceIsDisable() { - doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); - mPreConnectedDeviceController.setPreferenceSize(1); - mPreConnectedDeviceController.onDeviceRemoved(mPreference); + public void onDeviceAdded_addFourDevicePreference_onlyDisplayThree() { + mPreConnectedDeviceController.onDeviceAdded(new Preference(mContext)); + mPreConnectedDeviceController.onDeviceAdded(new Preference(mContext)); + mPreConnectedDeviceController.onDeviceAdded(new Preference(mContext)); + mPreConnectedDeviceController.onDeviceAdded(new Preference(mContext)); - assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreferenceGroup.isVisible()).isTrue(); + assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3); } + + @Test + public void onDeviceRemoved_removeLastDevice_setInvisible() { + final Preference preference = new Preference(mContext); + mPreferenceGroup.addPreference(preference); + mPreferenceGroup.setVisible(true); + + mPreConnectedDeviceController.onDeviceRemoved(preference); + + assertThat(mPreferenceGroup.isVisible()).isFalse(); + assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0); + } + } From 28fe954075f2a0a7ae700809ec83dc898f77f67c Mon Sep 17 00:00:00 2001 From: Neil Fuller Date: Tue, 2 Oct 2018 11:55:20 +0100 Subject: [PATCH 2/7] Avoid deprecated PrivateKeyInfo.getAlgorithmId() Switch from using the PrivateKeyInfo.getAlgorithmId() method to using the functionally identical PrivateKeyInfo.getPrivateKeyAlgorithm() method instead. Bug: 113148576 Test: build only / inspection Merged-In: Iab2d3b23ad969f683d716cb981e32fd554c67a81 Change-Id: I9ed5c96a20d6749bf75212c0fd820d71ca0a6053 --- src/com/android/settings/CredentialStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java index ffbd2ce409b..0a559c2bd9a 100644 --- a/src/com/android/settings/CredentialStorage.java +++ b/src/com/android/settings/CredentialStorage.java @@ -218,7 +218,7 @@ public final class CredentialStorage extends Activity { try { ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData)); PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject()); - String algOid = pki.getAlgorithmId().getAlgorithm().getId(); + String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId(); String algName = new AlgorithmId(new ObjectIdentifier(algOid)).getName(); return KeyChain.isBoundKeyAlgorithm(algName); From 8800b9e1ffba134143f1f6f47c8cbbace632177a Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 24 Sep 2018 18:09:01 -0700 Subject: [PATCH 3/7] Use isConnected in CachedBluetoothDevice This method check whether it has connected profile, not physical connection between devices. It make more sense to use it in settings: 1. We only care about whether it has active profile(i.e. A2dp, hfp..) 2. Sometime when profile is disconnected, BluetoothDevice.isConnected() still return true. This may make UI flaky. Bug: 79947085 Test: RunSettingsRoboTests Change-Id: I44039704508a742c7a8aef0a035afcf169b08939 --- .../settings/bluetooth/BluetoothDeviceUpdater.java | 2 +- .../settings/bluetooth/SavedBluetoothDeviceUpdater.java | 2 +- .../settings/bluetooth/BluetoothDeviceUpdaterTest.java | 4 ++-- .../bluetooth/SavedBluetoothDeviceUpdaterTest.java | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 4cc329cd356..cac4565bddb 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -272,6 +272,6 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, ", is connected : " + device.isConnected() + " , is profile connected : " + cachedDevice.isConnected()); } - return device.getBondState() == BluetoothDevice.BOND_BONDED && device.isConnected(); + return device.getBondState() == BluetoothDevice.BOND_BONDED && cachedDevice.isConnected(); } } diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java index 6d034fda96f..127cb03e18f 100644 --- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java @@ -46,7 +46,7 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater ", is connected : " + device.isConnected() + ", is profile connected : " + cachedDevice.isConnected()); } - return device.getBondState() == BluetoothDevice.BOND_BONDED && !device.isConnected(); + return device.getBondState() == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected(); } @Override diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java index 28bf3ab8903..4ede9477995 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java @@ -161,7 +161,7 @@ public class BluetoothDeviceUpdaterTest { @Test public void isDeviceConnected_deviceConnected() { doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); - doReturn(true).when(mBluetoothDevice).isConnected(); + doReturn(true).when(mCachedBluetoothDevice).isConnected(); assertThat(mBluetoothDeviceUpdater.isDeviceConnected(mCachedBluetoothDevice)).isTrue(); } @@ -169,7 +169,7 @@ public class BluetoothDeviceUpdaterTest { @Test public void isDeviceConnected_deviceNotConnected() { doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); - doReturn(false).when(mBluetoothDevice).isConnected(); + doReturn(false).when(mCachedBluetoothDevice).isConnected(); assertThat(mBluetoothDeviceUpdater.isDeviceConnected(mCachedBluetoothDevice)).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java index e25e4b22744..4d4a711ff5e 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java @@ -77,7 +77,7 @@ public class SavedBluetoothDeviceUpdaterTest { @Test public void update_filterMatch_addPreference() { doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState(); - doReturn(false).when(mBluetoothDevice).isConnected(); + doReturn(false).when(mCachedBluetoothDevice).isConnected(); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); @@ -87,7 +87,7 @@ public class SavedBluetoothDeviceUpdaterTest { @Test public void update_filterNotMatch_removePreference() { doReturn(BluetoothDevice.BOND_NONE).when(mBluetoothDevice).getBondState(); - doReturn(true).when(mBluetoothDevice).isConnected(); + doReturn(true).when(mCachedBluetoothDevice).isConnected(); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); @@ -96,7 +96,7 @@ public class SavedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceConnected_removePreference() { - when(mBluetoothDevice.isConnected()).thenReturn(true); + when(mCachedBluetoothDevice.isConnected()).thenReturn(true); mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP); @@ -106,7 +106,7 @@ public class SavedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceDisconnected_addPreference() { - when(mBluetoothDevice.isConnected()).thenReturn(false); + when(mCachedBluetoothDevice.isConnected()).thenReturn(false); mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP); From 10f519347aa535ff72e7cb196f703699dbfd05bc Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Wed, 3 Oct 2018 11:01:04 +0800 Subject: [PATCH 4/7] Delete provider data before insert - Updated CardContentProvider and deleted exist data first to avoid redundant insertion in bulkInsert Bug: 115575311 Test: robotest Change-Id: I58b7b26f18bfdb8a59ebc61d7abb478390c58e4c --- .../settings/homepage/CardContentProvider.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/homepage/CardContentProvider.java b/src/com/android/settings/homepage/CardContentProvider.java index 7b087568ca1..99894459adf 100644 --- a/src/com/android/settings/homepage/CardContentProvider.java +++ b/src/com/android/settings/homepage/CardContentProvider.java @@ -77,8 +77,11 @@ public class CardContentProvider extends ContentProvider { final String table = getTableFromMatch(uri); database.beginTransaction(); + + // Here deletion first is avoiding redundant insertion. According to cl/215350754 + database.delete(table, null /* whereClause */, null /* whereArgs */); for (ContentValues value : values) { - long ret = database.insert(table, null, value); + long ret = database.insert(table, null /* nullColumnHack */, value); if (ret != -1L) { numInserted++; } else { @@ -87,7 +90,7 @@ public class CardContentProvider extends ContentProvider { } } database.setTransactionSuccessful(); - getContext().getContentResolver().notifyChange(uri, null); + getContext().getContentResolver().notifyChange(uri, null /* observer */); } finally { database.endTransaction(); StrictMode.setThreadPolicy(oldPolicy); @@ -103,7 +106,7 @@ public class CardContentProvider extends ContentProvider { final SQLiteDatabase database = mDBHelper.getWritableDatabase(); final String table = getTableFromMatch(uri); final int rowsDeleted = database.delete(table, selection, selectionArgs); - getContext().getContentResolver().notifyChange(uri, null); + getContext().getContentResolver().notifyChange(uri, null /* observer */); return rowsDeleted; } finally { StrictMode.setThreadPolicy(oldPolicy); @@ -127,7 +130,8 @@ public class CardContentProvider extends ContentProvider { queryBuilder.setTables(table); final SQLiteDatabase database = mDBHelper.getReadableDatabase(); final Cursor cursor = queryBuilder.query(database, - projection, selection, selectionArgs, null, null, sortOrder); + projection, selection, selectionArgs, null /* groupBy */, null /* having */, + sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; @@ -145,7 +149,7 @@ public class CardContentProvider extends ContentProvider { final SQLiteDatabase database = mDBHelper.getWritableDatabase(); final String table = getTableFromMatch(uri); final int rowsUpdated = database.update(table, values, selection, selectionArgs); - getContext().getContentResolver().notifyChange(uri, null); + getContext().getContentResolver().notifyChange(uri, null /* observer */); return rowsUpdated; } finally { StrictMode.setThreadPolicy(oldPolicy); From a6cec4732605ec618e902c73bbb95e5ace983642 Mon Sep 17 00:00:00 2001 From: felkachang Date: Thu, 29 Mar 2018 12:08:20 +0800 Subject: [PATCH 5/7] Change reset page to be SUW style The factory reset page and the reset confirmation page is too old to follow the style of Setup Wizard design. To change the layout and apply the style for textviews and header. Bug: 73738836 Test: make -j SettingsRoboTests RunSettingsRoboTests Change-Id: I1ee3d09e1ef9cac8e25c60a566363d4f7d537eeb --- res/drawable/ic_delete_accent.xml | 31 ++++ res/layout/master_clear.xml | 153 +++++++++--------- res/layout/master_clear_account.xml | 5 +- res/layout/master_clear_confirm.xml | 42 ++--- res/layout/reset_esim_checkbox.xml | 15 +- res/values/dimens.xml | 2 +- res/values/styles.xml | 5 - src/com/android/settings/MasterClear.java | 104 ++++++++---- .../android/settings/MasterClearConfirm.java | 44 ++++- .../com/android/settings/MasterClearTest.java | 50 +++--- 10 files changed, 272 insertions(+), 179 deletions(-) create mode 100644 res/drawable/ic_delete_accent.xml diff --git a/res/drawable/ic_delete_accent.xml b/res/drawable/ic_delete_accent.xml new file mode 100644 index 00000000000..86a9ccacd40 --- /dev/null +++ b/res/drawable/ic_delete_accent.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/res/layout/master_clear.xml b/res/layout/master_clear.xml index 921347e655e..247eedc407f 100644 --- a/res/layout/master_clear.xml +++ b/res/layout/master_clear.xml @@ -14,122 +14,125 @@ limitations under the License. --> - + android:orientation="vertical" + android:theme="@style/SuwThemeGlifV3.Light" + android:icon="@drawable/ic_delete_accent" + app:suwHeaderText="@string/master_clear_title"> + android:id="@+id/master_clear_scrollview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/reset_master_clear_margin_start" + android:layout_marginEnd="@dimen/reset_master_clear_margin_end"> + + - + - + - + - + android:text="@string/master_clear_accounts"/> + - - + - + - + + + - - + android:layout_gravity="center_vertical" + android:orientation="vertical"> + android:text="@string/erase_external_storage"/> + android:text="@string/erase_external_storage_description"/> - + -