Add CTS to verify IMS.onStartInput() is called once.

Also added mechanism to serialize/deserialize IMS event extras.

Test: cts-tradefed run singleCommand cts-dev --module
CtsInputMethodServiceHostTestCases --test
android.inputmethodservice.cts.hostside.InputMethodServiceLifecycleTest#testOnStartInputCalledOnce

Bug: 35599628
Change-Id: I5fd7d8a535d66b7599bb03d7192668d00e10d81c
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
index 9927ca9..307693a 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
@@ -50,6 +50,12 @@
     public static final String EXTRA_EVENT_SENDER = "event_sender";
 
     /**
+     * Intent extra key for Event parameters like
+     * {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
+     */
+    public static final String EXTRA_EVENT_PARAMS = "event_params";
+
+    /**
      * Intent extra key for what type a device event is. Values are {@link DeviceEventType#name()}.
      *
      * @see android.content.Intent#putExtra(String,String)
@@ -67,6 +73,29 @@
     public static final String EXTRA_EVENT_TIME = "event_time";
 
     /**
+     * Parameter for {@link DeviceEventType}.
+     */
+    public enum DeviceEventTypeParam {
+
+        /**
+         *  Param for {@link DeviceEventType#ON_START_INPUT}. Represents if IME is restarting.
+         */
+        ON_START_INPUT_RESTARTING(DeviceEventType.ON_START_INPUT, "onStartInput.restarting");
+
+        private final DeviceEventType mType;
+        private final String mName;
+
+        DeviceEventTypeParam(DeviceEventType type, String name) {
+            mType = type;
+            mName = name;
+        }
+
+        public String getName() {
+            return mName;
+        }
+    }
+
+    /**
      * Types of device event, a value of {@link #EXTRA_EVENT_TYPE}.
      */
     public enum DeviceEventType {
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
index 172e1a7..9288051 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
@@ -58,6 +58,9 @@
         // This is constants holding class, can't instantiate.
         private EventTableConstants() {}
 
+        /** Column name of the table that holds Event extras in json format. */
+        public static final String EXTRAS = "extras";
+
         /** Name of the table in content provider and database. */
         public static final String NAME = "events";
 
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
index ad48a46..aa90e11 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
@@ -47,4 +47,6 @@
             = "testSearchView_giveFocusShowIme1";
     public static final String TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1
             = "testSearchView_setQueryHideIme1";
+    public static final String TEST_ON_START_INPUT_CALLED_ONCE_IME1
+            = "testOnStartInputCalledOnceIme1";
 }
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
index 8278943..e5613ff 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
@@ -16,6 +16,10 @@
 
 package android.inputmethodservice.cts.devicetest;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import static android.inputmethodservice.cts.DeviceEvent.isFrom;
 import static android.inputmethodservice.cts.DeviceEvent.isNewerThan;
 import static android.inputmethodservice.cts.DeviceEvent.isType;
@@ -35,6 +39,7 @@
 import android.app.Activity;
 import android.inputmethodservice.cts.DeviceEvent;
 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
 import android.inputmethodservice.cts.common.Ime1Constants;
 import android.inputmethodservice.cts.common.Ime2Constants;
 import android.inputmethodservice.cts.common.test.DeviceTestConstants;
@@ -48,10 +53,12 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.IntFunction;
 import java.util.function.Predicate;
 import java.util.stream.Collector;
+import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
 public class InputMethodServiceDeviceTest {
@@ -185,6 +192,38 @@
                 TIMEOUT, "CtsInputMethod1.hideSoftInput is called");
     }
 
+    @Test
+    public void testOnStartInputCalledOnceIme1() throws Exception {
+        final TestHelper helper = new TestHelper(
+                getClass(), DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
+
+        helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
+        helper.findUiObject(R.id.text_entry).click();
+
+        // we should've only one onStartInput call.
+        pollingCheck(() -> helper.queryAllEvents()
+                        .collect(startingFrom(helper.isStartOfTest()))
+                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
+                        .findAny()
+                        .isPresent(),
+                TIMEOUT, "CtsInputMethod1.onStartInput is called");
+        List<DeviceEvent> startInputEvents = helper.queryAllEvents()
+                .collect(startingFrom(helper.isStartOfTest()))
+                .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
+                .collect(Collectors.toList());
+
+        assertEquals("CtsInputMethod1.onStartInput is called exactly once",
+                startInputEvents.size(),
+                1);
+
+        // check if that single event didn't cause IME restart.
+        final DeviceEvent event = startInputEvents.get(0);
+        Boolean isRestarting = DeviceEvent.getEventParamBoolean(
+                        DeviceEventTypeParam.ON_START_INPUT_RESTARTING, event);
+        assertTrue(isRestarting != null);
+        assertFalse(isRestarting);
+    }
+
     /**
      * Build stream collector of {@link DeviceEvent} collecting sequence that elements have
      * specified types.
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
index d9d4724..6977d6b 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
@@ -19,6 +19,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-annotations \
+    json \
     CtsInputMethodServiceCommon
 
 LOCAL_MODULE_TAGS := tests
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
index b6098f9..ff412ef 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
@@ -17,17 +17,20 @@
 package android.inputmethodservice.cts;
 
 import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_PARAMS;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
 
+
 import android.content.ContentValues;
 import android.content.Intent;
 import android.database.Cursor;
 import android.inputmethodservice.cts.common.DeviceEventConstants;
 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
 import android.inputmethodservice.cts.common.test.TestInfo;
 import android.inputmethodservice.cts.db.Entity;
@@ -35,8 +38,16 @@
 import android.inputmethodservice.cts.db.Table;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.json.stream.JsonReader;
+import com.android.json.stream.JsonWriter;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -51,21 +62,85 @@
 
     public static final Table<DeviceEvent> TABLE = new DeviceEventTable(EventTableConstants.NAME);
 
+    public static IntentBuilder builder() {
+        return new IntentBuilder();
+    }
+
     /**
-     * Create an intent to send a device event.
-     * @param sender an event sender.
-     * @param type an event type defined at {@link DeviceEventType}.
-     * @return an intent that has event {@code sender}, {@code type}, time from
-     *         {@link SystemClock#uptimeMillis()}, and target component of event receiver.
+     * Builder to create an intent to send a device event.
+     * The built intent that has event {@code sender}, {@code type}, {@code paramsString}, time from
+     * {@link SystemClock#uptimeMillis()}, and target component of event receiver.
+     *
      */
-    public static Intent newDeviceEventIntent(@NonNull final String sender,
-            @NonNull final DeviceEventType type) {
-        return new Intent()
-                .setAction(ACTION_DEVICE_EVENT)
-                .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
-                .putExtra(EXTRA_EVENT_SENDER, sender)
-                .putExtra(EXTRA_EVENT_TYPE, type.name())
-                .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
+    public static final class IntentBuilder {
+        String mSender;
+        DeviceEventType mType;
+        JsonWriter mJsonWriter;
+        StringWriter mStringWriter;
+
+        /**
+         * @param type an event type defined at {@link DeviceEventType}.
+         */
+        public IntentBuilder setType(DeviceEventType type) {
+            mType = type;
+            return this;
+        }
+
+        /**
+         * @param sender an event sender.
+         */
+        public void setSender(String sender) {
+            mSender = sender;
+        }
+
+        public IntentBuilder with(DeviceEventTypeParam eventParam, boolean value) {
+            appendToJson(eventParam, value);
+            return this;
+        }
+
+        public Intent build() {
+            Intent intent = new Intent()
+                    .setAction(ACTION_DEVICE_EVENT)
+                    .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
+                    .putExtra(EXTRA_EVENT_SENDER, mSender)
+                    .putExtra(EXTRA_EVENT_TYPE, mType.name())
+                    .putExtra(EXTRA_EVENT_PARAMS, getJsonString())
+                    .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
+
+            mJsonWriter = null;
+            mStringWriter = null;
+            return intent;
+        }
+
+        private String getJsonString() {
+            if (mJsonWriter == null) {
+                return "";
+            }
+            try {
+                mJsonWriter.endObject();
+                mJsonWriter.flush();
+            } catch (IOException e) {
+                throw new RuntimeException("IntentBuilder.getJsonString() failed.", e);
+            }
+            return mStringWriter.toString();
+        }
+
+        private void appendToJson(DeviceEventTypeParam eventParam, boolean value) {
+            final String key = eventParam.getName();
+            if (TextUtils.isEmpty(key)) {
+                return;
+            }
+            try {
+                if (mJsonWriter == null) {
+                    mStringWriter = new StringWriter();
+                    mJsonWriter = new JsonWriter(mStringWriter);
+                    mJsonWriter.beginObject();
+                }
+                mJsonWriter.name(key).value(value);
+            } catch (IOException e) {
+                throw new RuntimeException("IntentBuilder.appendToJson() failed.", e);
+            }
+        }
     }
 
     /**
@@ -93,7 +168,16 @@
                     "Intent must have " + EXTRA_EVENT_TIME + ": " + intent);
         }
 
-        return new DeviceEvent(sender, type, intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
+        String paramsString = intent.getStringExtra(DeviceEventConstants.EXTRA_EVENT_PARAMS);
+        if (paramsString == null) {
+            paramsString = "";
+        }
+
+        return new DeviceEvent(
+                sender,
+                type,
+                paramsString,
+                intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
     }
 
     /**
@@ -158,13 +242,21 @@
     public final DeviceEventType type;
 
     /**
+     * Event parameters formatted as json string.
+     * e.g. {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
+     */
+    public final String paramsString;
+
+    /**
      * Event time, value is from {@link SystemClock#uptimeMillis()}.
      */
     public final long time;
 
-    private DeviceEvent(final String sender, final DeviceEventType type, final long time) {
+    private DeviceEvent(
+            final String sender, final DeviceEventType type, String paramsString, final long time) {
         this.sender = sender;
         this.type = type;
+        this.paramsString = paramsString;
         this.time = time;
     }
 
@@ -174,6 +266,37 @@
     }
 
     /**
+     * @param eventParam {@link DeviceEventTypeParam} to look for.
+     * @param event {@link DeviceEvent} to look in.
+     * @return Event parameter for provided key. If key is not found in
+     * {@link DeviceEvent#paramsString}, null is returned.
+     *
+     * TODO: Support other primitive and custom types.
+     */
+    @Nullable
+    public static Boolean getEventParamBoolean(
+            DeviceEventTypeParam eventParam, final DeviceEvent event) {
+        StringReader stringReader = new StringReader(event.paramsString);
+        JsonReader reader = new JsonReader(stringReader);
+
+        try {
+            reader.beginObject();
+            while (reader.hasNext()) {
+                String name = reader.nextName();
+                if (name.equals(eventParam.getName())) {
+                    Boolean value = reader.nextBoolean();
+                    reader.endObject();
+                    return value;
+                }
+            }
+            reader.endObject();
+        } catch (IOException e) {
+            throw new RuntimeException("DeviceEvent.getEventParamBoolean() failed.", e);
+        }
+        return null;
+    }
+
+    /**
      * Abstraction of device event table in database.
      */
     private static final class DeviceEventTable extends Table<DeviceEvent> {
@@ -183,16 +306,19 @@
         private final Field SENDER;
         private final Field TYPE;
         private final Field TIME;
+        private final Field PARAMS;
 
         private DeviceEventTable(final String name) {
             super(name, new Entity.Builder<DeviceEvent>()
                     .addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
                     .addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
                     .addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
+                    .addField(EventTableConstants.EXTRAS, Cursor.FIELD_TYPE_STRING)
                     .build());
             SENDER = getField(EventTableConstants.SENDER);
             TYPE = getField(EventTableConstants.TYPE);
             TIME = getField(EventTableConstants.TIME);
+            PARAMS = getField(EventTableConstants.EXTRAS);
         }
 
         @Override
@@ -200,6 +326,7 @@
             final ContentValues values = new ContentValues();
             SENDER.putString(values, event.sender);
             TYPE.putString(values, event.type.name());
+            PARAMS.putString(values, event.paramsString);
             TIME.putLong(values, event.time);
             return values;
         }
@@ -214,6 +341,7 @@
                 final DeviceEvent event = new DeviceEvent(
                         SENDER.getString(cursor),
                         DeviceEventType.valueOf(TYPE.getString(cursor)),
+                        PARAMS.getString(cursor),
                         TIME.getLong(cursor));
                 builder.accept(event);
                 if (DEBUG_STREAM) {
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
index 05a7a1d..b37873c 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
@@ -25,11 +25,11 @@
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
 
-import android.content.Intent;
 import android.inputmethodservice.InputMethodService;
 import android.inputmethodservice.cts.DeviceEvent;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
 import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
+
 import android.os.ResultReceiver;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
@@ -48,7 +48,7 @@
     private class CtsInputMethodImpl extends InputMethodImpl {
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-            sendEvent(SHOW_SOFT_INPUT);
+            sendEvent(DeviceEvent.builder().setType(SHOW_SOFT_INPUT));
             if (DEBUG) {
                 Log.d(mLogTag, "showSoftInput called");
             }
@@ -57,7 +57,7 @@
 
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
-            sendEvent(HIDE_SOFT_INPUT);
+            sendEvent(DeviceEvent.builder().setType(HIDE_SOFT_INPUT));
             if (DEBUG) {
                 Log.d(mLogTag, "hideSoftInput called");
             }
@@ -71,7 +71,7 @@
         if (DEBUG) {
             Log.d(mLogTag, "onCreate:");
         }
-        sendEvent(ON_CREATE);
+        sendEvent(DeviceEvent.builder().setType(ON_CREATE));
 
         super.onCreate();
 
@@ -85,8 +85,10 @@
                     + " editorInfo=" + editorInfo
                     + " restarting=" + restarting);
         }
-        sendEvent(ON_START_INPUT, editorInfo, restarting);
 
+        sendEvent(DeviceEvent.builder()
+                .setType(ON_START_INPUT)
+                .with(DeviceEventTypeParam.ON_START_INPUT_RESTARTING, restarting));
         super.onStartInput(editorInfo, restarting);
     }
 
@@ -97,7 +99,8 @@
                     + " editorInfo=" + editorInfo
                     + " restarting=" + restarting);
         }
-        sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
+
+        sendEvent(DeviceEvent.builder().setType(ON_START_INPUT_VIEW));
 
         super.onStartInputView(editorInfo, restarting);
     }
@@ -107,7 +110,7 @@
         if (DEBUG) {
             Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
         }
-        sendEvent(ON_FINISH_INPUT_VIEW, finishingInput);
+        sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT_VIEW));
 
         super.onFinishInputView(finishingInput);
     }
@@ -117,7 +120,7 @@
         if (DEBUG) {
             Log.d(mLogTag, "onFinishInput:");
         }
-        sendEvent(ON_FINISH_INPUT);
+        sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT));
 
         super.onFinishInput();
     }
@@ -127,7 +130,7 @@
         if (DEBUG) {
             Log.d(mLogTag, "onDestroy:");
         }
-        sendEvent(ON_DESTROY);
+        sendEvent(DeviceEvent.builder().setType(ON_DESTROY));
 
         super.onDestroy();
 
@@ -175,10 +178,8 @@
         }
     }
 
-    private void sendEvent(final DeviceEventType type, final Object... args) {
-        final String sender = getClass().getName();
-        final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
-        // TODO: Send arbitrary {@code args} in {@code intent}.
-        sendBroadcast(intent);
+   private void sendEvent(final DeviceEvent.IntentBuilder intentBuilder) {
+        intentBuilder.setSender(getClass().getName());
+        sendBroadcast(intentBuilder.build());
     }
 }
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
index 2f76357..02c3e55 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -122,6 +122,17 @@
         assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
     }
 
+    @Test
+    public void testOnStartInputCalledOnce() throws Exception {
+        installAndSetIme1();
+
+        final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
+                DeviceTestConstants.TEST_CLASS,
+                DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
+        sendTestStartEvent(testSetQueryHideIme1);
+        assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
+    }
+
     private void installAndSetIme1() throws Exception {
         final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
             DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);