OSDN Git Service

SDK Manager: change initial selection logic.
authorRaphael <raphael@google.com>
Tue, 18 Oct 2011 17:31:38 +0000 (10:31 -0700)
committerRaphael <raphael@google.com>
Tue, 18 Oct 2011 17:31:38 +0000 (10:31 -0700)
This changes the way packages are selected:
- When using the "Select New" link, all new stuff is selected.
- When using the "Select Update" link, only installed stuff
  with updates is selected.
- When starts the SDK Manager, the heuristic becomes:
  - Select any updates we can find.
  - Check whether the top platform is installed.
  - If not at all, select all its packages. This should
    cover the "there's a new platform available" scenario
    by just selecting to install everything for it.
  - If the top platform has at least one item selected,
    make sure the platform package itself is installed or
    select it.
  - If the platform currently installed or a selected
    update lacks a system image, we want to suggest some
    kind of system image. For that, look whether the platform
    (current or selected update) provides one. If they don't
    select any system image package we can find.
  - On Windows, also suggest to install the USB driver.

SDK Bug: 20607

Change-Id: Ifab423b226ad708e9117eefd4d76033d866c57a8

sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java

index dc1c73a..42981a6 100755 (executable)
 package com.android.sdkuilib.internal.repository.sdkman2;
 
 import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.repository.ExtraPackage;
 import com.android.sdklib.internal.repository.IPackageVersion;
 import com.android.sdklib.internal.repository.Package;
 import com.android.sdklib.internal.repository.PlatformPackage;
 import com.android.sdklib.internal.repository.PlatformToolPackage;
 import com.android.sdklib.internal.repository.SdkSource;
+import com.android.sdklib.internal.repository.SystemImagePackage;
 import com.android.sdklib.internal.repository.ToolPackage;
 import com.android.sdklib.internal.repository.Package.UpdateInfo;
 import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.util.SparseArray;
 import com.android.sdkuilib.internal.repository.UpdaterData;
 import com.android.sdkuilib.internal.repository.sdkman2.PkgItem.PkgState;
 
@@ -34,11 +38,9 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -89,13 +91,24 @@ class PackagesDiffLogic {
      * <li> Always select the top platform and all its packages.
      * <li> If some platform is partially installed, selected anything new/update for it.
      * </ul>
+     *
+     * @param selectNew If true, select all new packages
+     * @param selectUpdates If true, select all update packages
+     * @param selectTop If true, select the top platform. If the top platform has noting installed,
+     *   select all items in it; if it is partially installed, at least select the platform and
+     *   at system images if none of the system images are installed.
+     * @param currentPlatform The {@link SdkConstants#currentPlatform()} value.
      */
-    public void checkNewUpdateItems(boolean selectNew, boolean selectUpdates) {
+    public void checkNewUpdateItems(
+            boolean selectNew,
+            boolean selectUpdates,
+            boolean selectTop,
+            int currentPlatform) {
         int maxApi = 0;
         Set<Integer> installedPlatforms = new HashSet<Integer>();
-        Map<Integer, List<PkgItem>> platformItems = new HashMap<Integer, List<PkgItem>>();
+        SparseArray<List<PkgItem>> platformItems = new SparseArray<List<PkgItem>>();
 
-        // sort items in platforms... directly deal with items with no platform
+        // sort items in platforms... directly deal with new/update items
         for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) {
             if (!item.hasCompatibleArchive()) {
                 // Ignore items that have no archive compatible with the current platform.
@@ -110,47 +123,119 @@ class PackagesDiffLogic {
                 api = ((IPackageVersion) p).getVersion().getApiLevel();
             }
 
-            if (api > 0) {
+            if (selectTop && api > 0) {
+                // Keep track of the max api seen
                 maxApi = Math.max(maxApi, api);
 
-                // keep track of what platform is currently installed and its items
+                // keep track of what platform is currently installed (that is, has at least
+                // one thing installed.)
                 if (item.getState() == PkgState.INSTALLED) {
                     installedPlatforms.add(api);
                 }
+
+                // for each platform, collect all its related item for later use below.
                 List<PkgItem> items = platformItems.get(api);
                 if (items == null) {
                     platformItems.put(api, items = new ArrayList<PkgItem>());
                 }
                 items.add(item);
-            } else {
-                // not a plaform package...
-                if ((selectNew && item.getState() == PkgState.NEW) ||
-                        (selectUpdates && item.hasUpdatePkg())) {
-                    item.setChecked(true);
-                }
+            }
+
+            if ((selectNew && item.getState() == PkgState.NEW) ||
+                    (selectUpdates && item.hasUpdatePkg())) {
+                item.setChecked(true);
             }
         }
 
-        // If there are some platforms installed. Pickup anything new in them.
-        for (Integer api : installedPlatforms) {
-            List<PkgItem> items = platformItems.get(api);
-            if (items != null) {
+        List<PkgItem> items = platformItems.get(maxApi);
+        if (selectTop && maxApi > 0 && items != null) {
+            if (!installedPlatforms.contains(maxApi)) {
+                // If the top platform has nothing installed at all, select everything in it
                 for (PkgItem item : items) {
-                    if ((selectNew && item.getState() == PkgState.NEW) ||
-                            (selectUpdates && item.hasUpdatePkg())) {
+                    if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
                         item.setChecked(true);
                     }
                 }
+
+            } else {
+                // The top platform has at least one thing installed.
+
+                // First make sure the platform package itself is installed, or select it.
+                for (PkgItem item : items) {
+                     Package p = item.getMainPackage();
+                     if (p instanceof PlatformPackage && item.getState() == PkgState.NEW) {
+                         item.setChecked(true);
+                         break;
+                     }
+                }
+
+                // Check we have at least one system image installed, otherwise select them
+                boolean hasSysImg = false;
+                for (PkgItem item : items) {
+                    Package p = item.getMainPackage();
+                    if (p instanceof PlatformPackage && item.getState() == PkgState.INSTALLED) {
+                        if (item.hasUpdatePkg() && item.isChecked()) {
+                            // If the installed platform is schedule for update, look for the
+                            // system image in the update package, not the current one.
+                            p = item.getUpdatePkg();
+                            if (p instanceof PlatformPackage) {
+                                hasSysImg = ((PlatformPackage) p).getIncludedAbi() != null;
+                            }
+                        } else {
+                            // Otherwise look into the currently installed platform
+                            hasSysImg = ((PlatformPackage) p).getIncludedAbi() != null;
+                        }
+                        if (hasSysImg) {
+                            break;
+                        }
+                    }
+                    if (p instanceof SystemImagePackage && item.getState() == PkgState.INSTALLED) {
+                        hasSysImg = true;
+                        break;
+                    }
+                }
+                if (!hasSysImg) {
+                    // No system image installed.
+                    // Try whether the current platform or its update would bring one.
+
+                    for (PkgItem item : items) {
+                         Package p = item.getMainPackage();
+                         if (p instanceof PlatformPackage) {
+                             if (item.getState() == PkgState.NEW &&
+                                     ((PlatformPackage) p).getIncludedAbi() != null) {
+                                 item.setChecked(true);
+                                 hasSysImg = true;
+                             } else if (item.hasUpdatePkg()) {
+                                 p = item.getUpdatePkg();
+                                 if (p instanceof PlatformPackage &&
+                                         ((PlatformPackage) p).getIncludedAbi() != null) {
+                                     item.setChecked(true);
+                                     hasSysImg = true;
+                                 }
+                             }
+                         }
+                    }
+                }
+                if (!hasSysImg) {
+                    // No system image in the platform, try a system image package
+                    for (PkgItem item : items) {
+                        Package p = item.getMainPackage();
+                        if (p instanceof SystemImagePackage && item.getState() == PkgState.NEW) {
+                            item.setChecked(true);
+                        }
+                    }
+                }
             }
         }
 
-        // Whether we have platforms installed or not, select everything from the top platform.
-        if (maxApi > 0) {
-            List<PkgItem> items = platformItems.get(maxApi);
-            if (items != null) {
-                for (PkgItem item : items) {
-                    if ((selectNew && item.getState() == PkgState.NEW) ||
-                            (selectUpdates && item.hasUpdatePkg())) {
+        if (selectTop && currentPlatform == SdkConstants.PLATFORM_WINDOWS) {
+            // On Windows, we'll also auto-select the USB driver
+            for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) {
+                Package p = item.getMainPackage();
+                if (p instanceof ExtraPackage && item.getState() == PkgState.NEW) {
+                    ExtraPackage ep = (ExtraPackage) p;
+                    if (ep.getVendor().equals("google") &&          //$NON-NLS-1$
+                            ep.getPath().equals("usb_driver")) {    //$NON-NLS-1$
                         item.setChecked(true);
                     }
                 }
index 120fe4f..3c1ba8a 100755 (executable)
@@ -16,6 +16,7 @@
 
 package com.android.sdkuilib.internal.repository.sdkman2;
 
+import com.android.sdklib.SdkConstants;
 import com.android.sdklib.internal.repository.Archive;
 import com.android.sdklib.internal.repository.IDescription;
 import com.android.sdklib.internal.repository.ITask;
@@ -306,7 +307,7 @@ public class PackagesPage extends UpdaterPage
             public void widgetSelected(SelectionEvent e) {
                 super.widgetSelected(e);
                 boolean selectNew = e.text == null || e.text.equals(strLinkNew);
-                onSelectNewUpdates(selectNew, !selectNew);
+                onSelectNewUpdates(selectNew, !selectNew, false/*selectTop*/);
             }
         });
 
@@ -629,7 +630,10 @@ public class PackagesPage extends UpdaterPage
                                 // automatically select all new and update packages.
                                 Object[] checked = mTreeViewer.getCheckedElements();
                                 if (checked == null || checked.length == 0) {
-                                    onSelectNewUpdates(true, true);
+                                    onSelectNewUpdates(
+                                            false, //selectNew
+                                            true,  //selectUpdates,
+                                            true); //selectTop
                                 }
                             }
                         }
@@ -879,11 +883,16 @@ public class PackagesPage extends UpdaterPage
     }
 
     /**
-     * Checks all PkgItems that are either new or have updates.
+     * Checks all PkgItems that are either new or have updates or select top platform
+     * for initial run.
      */
-    private void onSelectNewUpdates(boolean selectNew, boolean selectUpdates) {
+    private void onSelectNewUpdates(boolean selectNew, boolean selectUpdates, boolean selectTop) {
         // This does not update the tree itself, syncViewerSelection does it below.
-        mDiffLogic.checkNewUpdateItems(selectNew, selectUpdates);
+        mDiffLogic.checkNewUpdateItems(
+                selectNew,
+                selectUpdates,
+                selectTop,
+                SdkConstants.CURRENT_PLATFORM);
         syncViewerSelection();
         updateButtonsState();
     }
index 499421f..589b39e 100755 (executable)
@@ -16,6 +16,7 @@
 
 package com.android.sdkuilib.internal.repository.sdkman2;
 
+import com.android.sdklib.SdkConstants;
 import com.android.sdklib.internal.repository.BrokenPackage;
 import com.android.sdklib.internal.repository.MockAddonPackage;
 import com.android.sdklib.internal.repository.MockBrokenPackage;
@@ -23,6 +24,7 @@ import com.android.sdklib.internal.repository.MockEmptyPackage;
 import com.android.sdklib.internal.repository.MockExtraPackage;
 import com.android.sdklib.internal.repository.MockPlatformPackage;
 import com.android.sdklib.internal.repository.MockPlatformToolPackage;
+import com.android.sdklib.internal.repository.MockSystemImagePackage;
 import com.android.sdklib.internal.repository.MockToolPackage;
 import com.android.sdklib.internal.repository.Package;
 import com.android.sdklib.internal.repository.SdkRepoSource;
@@ -836,7 +838,7 @@ public class PackagesDiffLogicTest extends TestCase {
                 getTree(m, false /*displaySortByApi*/));
 
         // Now request to check new items only
-        m.checkNewUpdateItems(true, false);
+        m.checkNewUpdateItems(true, false, false, SdkConstants.PLATFORM_LINUX);
 
         assertEquals(
                 "PkgCategoryApi <API=TOOLS, label=Tools, #items=0>\n" +
@@ -882,7 +884,7 @@ public class PackagesDiffLogicTest extends TestCase {
                 getTree(m, false /*displaySortByApi*/));
 
         // Now request to check update items only
-        m.checkNewUpdateItems(false, true);
+        m.checkNewUpdateItems(false, true, false, SdkConstants.PLATFORM_LINUX);
 
         assertEquals(
                 "PkgCategoryApi <API=TOOLS, label=Tools, #items=0>\n" +
@@ -901,7 +903,9 @@ public class PackagesDiffLogicTest extends TestCase {
 
     public void testCheckNewUpdateItems_SelectInitial() {
         // Populate the list with typical items: tools, platforms tools, extras, 2 platforms.
-        // With nothing installed, this should pick the tools, extras and the top platform.
+        // With nothing installed, this should pick the top platform and its system images
+        // (the mock platform claims to not have any included abi)
+        // It's ok not to select the tools, since they are a dependency of all platforms.
 
         SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
         SdkSource src2 = new SdkRepoSource("http://2.example.com/url2", "repo2");
@@ -913,9 +917,11 @@ public class PackagesDiffLogicTest extends TestCase {
         m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
                 new MockToolPackage(src1, 10, 3),
                 new MockPlatformToolPackage(src1, 3),
-                new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
+                new MockExtraPackage(src1, "google", "usb_driver", 5, 3),
                 p1 = new MockPlatformPackage(src1, 1, 2, 3),    // API 1
                 p2 = new MockPlatformPackage(src1, 2, 4, 3),    // API 2
+                new MockSystemImagePackage(src1, p2, 1, "armeabi"),
+                new MockSystemImagePackage(src1, p2, 1, "x86"),
         });
         m.updateSourcePackages(true /*sortByApi*/, src2, new Package[] {
                 new MockAddonPackage(src2, "addon A", p1, 5),
@@ -924,86 +930,94 @@ public class PackagesDiffLogicTest extends TestCase {
         });
         m.updateEnd(true /*sortByApi*/);
 
-        m.checkNewUpdateItems(true, true);
+        m.checkNewUpdateItems(false, true, true, SdkConstants.PLATFORM_LINUX);
 
         assertEquals(
                 "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
-                "-- < * NEW, pkg:Android SDK Tools, revision 10>\n" +
-                "-- < * NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
-                "PkgCategoryApi <API=API 2, label=Android android-2 (API 2), #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 10>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
+                "PkgCategoryApi <API=API 2, label=Android android-2 (API 2), #items=4>\n" +
                 "-- < * NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+                "-- < * NEW, pkg:ARM EABI System Image, Android API 2, revision 1>\n" +
+                "-- < * NEW, pkg:Intel x86 Atom System Image, Android API 2, revision 1>\n" +
                 "-- < * NEW, pkg:addon B by vendor 2, Android API 2, revision 7>\n" +
                 "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=2>\n" +
                 "-- <NEW, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
                 "-- <NEW, pkg:addon A by vendor 1, Android API 1, revision 5>\n" +
                 "PkgCategoryApi <API=EXTRAS, label=Extras, #items=2>\n" +
-                "-- < * NEW, pkg:Android USB Driver package, revision 5>\n" +
-                "-- < * NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+                "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n" +
+                "-- <NEW, pkg:Google USB Driver package, revision 5>\n",
                 getTree(m, true /*displaySortByApi*/));
         assertEquals(
-                "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
-                "-- < * NEW, pkg:Android SDK Tools, revision 10>\n" +
-                "-- < * NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
+                "PkgCategorySource <source=repo1 (1.example.com), #items=7>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 10>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
                 "-- < * NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
                 "-- <NEW, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
-                "-- < * NEW, pkg:Android USB Driver package, revision 5>\n" +
+                "-- < * NEW, pkg:ARM EABI System Image, Android API 2, revision 1>\n" +
+                "-- < * NEW, pkg:Intel x86 Atom System Image, Android API 2, revision 1>\n" +
+                "-- <NEW, pkg:Google USB Driver package, revision 5>\n" +
                 "PkgCategorySource <source=repo2 (2.example.com), #items=3>\n" +
                 "-- < * NEW, pkg:addon B by vendor 2, Android API 2, revision 7>\n" +
                 "-- <NEW, pkg:addon A by vendor 1, Android API 1, revision 5>\n" +
-                "-- < * NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+                "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
                 getTree(m, false /*displaySortByApi*/));
 
-        // API 1 was not suggested for install since it was totally uninstalled.
-        // Now if at least one item of API 1 is installed, the rest will be offered for install too.
+        // We don't install the USB driver by default on Mac or Linux, only on Windows
         m.clear();
         m.updateStart();
-        m.updateSourcePackages(true /*sortByApi*/, null /*source*/, new Package[] {
-                new MockToolPackage(src1, 10, 3),
-                // addon for API 1 is installed but not the platform
-                new MockAddonPackage(src2, "addon A", p1, 5),
-        });
         m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
-                new MockToolPackage(src1, 10, 3),
-                new MockPlatformToolPackage(src1, 3),
-                new MockExtraPackage(src1, "android", "usb_driver", 5, 3),
-                p1,    // API 1
-                p2,    // API 2
+                new MockExtraPackage(src1, "google", "usb_driver", 5, 3),
         });
-        m.updateSourcePackages(true /*sortByApi*/, src2, new Package[] {
-                new MockAddonPackage(src2, "addon A", p1, 5),
-                new MockAddonPackage(src2, "addon B", p2, 7),
-                new MockExtraPackage(src2, "carrier", "custom_rom", 1, 0),
+        m.updateEnd(true /*sortByApi*/);
+        m.checkNewUpdateItems(false, true, true, SdkConstants.PLATFORM_LINUX);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=0>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=1>\n" +
+                "-- <NEW, pkg:Google USB Driver package, revision 5>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=1>\n" +
+                "-- <NEW, pkg:Google USB Driver package, revision 5>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+        m.clear();
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockExtraPackage(src1, "google", "usb_driver", 5, 3),
         });
         m.updateEnd(true /*sortByApi*/);
+        m.checkNewUpdateItems(false, true, true, SdkConstants.PLATFORM_DARWIN);
 
-        m.checkNewUpdateItems(true, true);
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=0>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=1>\n" +
+                "-- <NEW, pkg:Google USB Driver package, revision 5>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=1>\n" +
+                "-- <NEW, pkg:Google USB Driver package, revision 5>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+        m.clear();
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockExtraPackage(src1, "google", "usb_driver", 5, 3),
+        });
+        m.updateEnd(true /*sortByApi*/);
+        m.checkNewUpdateItems(false, true, true, SdkConstants.PLATFORM_WINDOWS);
 
         assertEquals(
-                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
-                "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
-                "-- < * NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
-                "PkgCategoryApi <API=API 2, label=Android android-2 (API 2), #items=2>\n" +
-                "-- < * NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
-                "-- < * NEW, pkg:addon B by vendor 2, Android API 2, revision 7>\n" +
-                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=2>\n" +
-                "-- < * NEW, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
-                "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5>\n" +
-                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=2>\n" +
-                "-- < * NEW, pkg:Android USB Driver package, revision 5>\n" +
-                "-- < * NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=0>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=1>\n" +
+                "-- < * NEW, pkg:Google USB Driver package, revision 5>\n",
                 getTree(m, true /*displaySortByApi*/));
         assertEquals(
-                "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
-                "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
-                "-- < * NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
-                "-- < * NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
-                "-- < * NEW, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
-                "-- < * NEW, pkg:Android USB Driver package, revision 5>\n" +
-                "PkgCategorySource <source=repo2 (2.example.com), #items=3>\n" +
-                "-- < * NEW, pkg:addon B by vendor 2, Android API 2, revision 7>\n" +
-                "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5>\n" +
-                "-- < * NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+                "PkgCategorySource <source=repo1 (1.example.com), #items=1>\n" +
+                "-- < * NEW, pkg:Google USB Driver package, revision 5>\n",
                 getTree(m, false /*displaySortByApi*/));
+
     }
 
     public void testCheckUncheckAllItems() {