/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.sdkuilib.internal.repository.ui; import com.android.SdkConstants; import com.android.sdklib.internal.repository.DownloadCache; import com.android.sdklib.internal.repository.DownloadCache.Strategy; import com.android.sdklib.internal.repository.IDescription; import com.android.sdklib.internal.repository.archives.Archive; import com.android.sdklib.internal.repository.packages.Package; import com.android.sdklib.internal.repository.sources.SdkSource; import com.android.sdkuilib.internal.repository.UpdaterData; import com.android.sdkuilib.internal.repository.core.PackageLoader; import com.android.sdkuilib.internal.repository.core.PackageLoader.ISourceLoadedCallback; import com.android.sdkuilib.internal.repository.core.PackagesDiffLogic; import com.android.sdkuilib.internal.repository.core.PkgCategory; import com.android.sdkuilib.internal.repository.core.PkgCategoryApi; import com.android.sdkuilib.internal.repository.core.PkgContentProvider; import com.android.sdkuilib.internal.repository.core.PkgItem; import com.android.sdkuilib.internal.repository.core.PkgItem.PkgState; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.IInputProvider; import org.eclipse.jface.viewers.ITableFontProvider; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import java.net.MalformedURLException; import java.net.URL; import java.util.List; /** * Base class for {@link PackagesPage} that holds most of the logic to display * the tree/list of packages. This class holds most of the logic and {@link PackagesPage} * holds most of the UI (creating the UI, dealing with menus and buttons and tree * selection.) This makes it easier to test the functionality by mocking only a * subset of the UI. */ abstract class PackagesPageImpl { final UpdaterData mUpdaterData; final PackagesDiffLogic mDiffLogic; private ICheckboxTreeViewer mITreeViewer; private ITreeViewerColumn mIColumnName; private ITreeViewerColumn mIColumnApi; private ITreeViewerColumn mIColumnRevision; private ITreeViewerColumn mIColumnStatus; PackagesPageImpl(UpdaterData updaterData) { mUpdaterData = updaterData; mDiffLogic = new PackagesDiffLogic(updaterData); } /** * Utility method that derived classes can override to check whether the UI is disposed. * When the UI is disposed, most operations that affect the UI will be bypassed. * @return True if UI is not available and should not be touched. */ abstract protected boolean isUiDisposed(); /** * Utility method to execute a runnable on the main UI thread. * Will do nothing if {@link #isUiDisposed()} returns false. * @param runnable The runnable to execute on the main UI thread. */ abstract protected void syncExec(Runnable runnable); void performFirstLoad() { // First a package loader is created that only checks // the local cache xml files. It populates the package // list based on what the client got last, essentially. loadPackages(true /*useLocalCache*/, false /*overrideExisting*/); // Next a regular package loader is created that will // respect the expiration and refresh parameters of the // download cache. loadPackages(false /*useLocalCache*/, true /*overrideExisting*/); } public void setITreeViewer(ICheckboxTreeViewer iTreeViewer) { mITreeViewer = iTreeViewer; } public void setIColumns( ITreeViewerColumn columnName, ITreeViewerColumn columnApi, ITreeViewerColumn columnRevision, ITreeViewerColumn columnStatus) { mIColumnName = columnName; mIColumnApi = columnApi; mIColumnRevision = columnRevision; mIColumnStatus = columnStatus; } void postCreate() { // Caller needs to call setITreeViewer before this. assert mITreeViewer != null; // Caller needs to call setIColumns before this. assert mIColumnApi != null; assert mIColumnName != null; assert mIColumnStatus != null; assert mIColumnRevision != null; mITreeViewer.setContentProvider(new PkgContentProvider(mITreeViewer)); mIColumnApi.setLabelProvider( new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnApi))); mIColumnName.setLabelProvider( new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnName))); mIColumnStatus.setLabelProvider( new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnStatus))); mIColumnRevision.setLabelProvider( new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnRevision))); } /** * Performs a full reload by removing all cached packages data, including the platforms * and addons from the sdkmanager instance. This will perform a full local parsing * as well as a full reload of the remote data (by fetching all sources again.) */ void fullReload() { // Clear all source information, forcing them to be refreshed. mUpdaterData.getSources().clearAllPackages(); // Clear and reload all local data too. localReload(); } /** * Performs a full reload of all the local package information, including the platforms * and addons from the sdkmanager instance. This will perform a full local parsing. *
* This method does NOT force a new fetch of the remote sources. * * @see #fullReload() */ void localReload() { // Clear all source caches, otherwise loading will use the cached data mUpdaterData.getLocalSdkParser().clearPackages(); mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog()); loadPackages(); } /** * Performs a "normal" reload of the package information, use the default download * cache and refreshing strategy as needed. */ void loadPackages() { loadPackages(false /*useLocalCache*/, false /*overrideExisting*/); } /** * Performs a reload of the package information. * * @param useLocalCache When true, the {@link PackageLoader} is switched to use * a specific {@link DownloadCache} using the {@link Strategy#ONLY_CACHE}, meaning * it will only use data from the local cache. It will not try to fetch or refresh * manifests. This is used once the very first time the sdk manager window opens * and is typically followed by a regular load with refresh. */ abstract protected void loadPackages(boolean useLocalCache, boolean overrideExisting); /** * Actual implementation of {@link #loadPackages(boolean, boolean)}. * Derived implementations must call this to do the actual work after setting up the UI. */ void loadPackagesImpl(final boolean useLocalCache, final boolean overrideExisting) { if (mUpdaterData == null) { return; } final boolean displaySortByApi = isSortByApi(); PackageLoader packageLoader = getPackageLoader(useLocalCache); assert packageLoader != null; mDiffLogic.updateStart(); packageLoader.loadPackages(overrideExisting, new ISourceLoadedCallback() { @Override public boolean onUpdateSource(SdkSource source, Package[] newPackages) { // This runs in a thread and must not access UI directly. final boolean changed = mDiffLogic.updateSourcePackages( displaySortByApi, source, newPackages); syncExec(new Runnable() { @Override public void run() { if (changed || mITreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) { refreshViewerInput(); } } }); // Return true to tell the loader to continue with the next source. // Return false to stop the loader if any UI has been disposed, which can // happen if the user is trying to close the window during the load operation. return !isUiDisposed(); } @Override public void onLoadCompleted() { // This runs in a thread and must not access UI directly. final boolean changed = mDiffLogic.updateEnd(displaySortByApi); syncExec(new Runnable() { @Override public void run() { if (changed || mITreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) { try { refreshViewerInput(); } catch (Exception ignore) {} } if (!useLocalCache && mDiffLogic.isFirstLoadComplete() && !isUiDisposed()) { // At the end of the first load, if nothing is selected then // automatically select all new and update packages. Object[] checked = mITreeViewer.getCheckedElements(); if (checked == null || checked.length == 0) { onSelectNewUpdates( false, //selectNew true, //selectUpdates, true); //selectTop } } } }); } }); } /** * Used by {@link #loadPackagesImpl(boolean, boolean)} to get the package * loader for the first or second pass update. When starting the manager * starts with a first pass that reads only from the local cache, with no * extra network access. That's {@code useLocalCache} being true. * * Leter it does a second pass with {@code useLocalCache} set to false * and actually uses the download cache specified in {@link UpdaterData}. * * This is extracted so that we can control this cache via unit tests. */ protected PackageLoader getPackageLoader(boolean useLocalCache) { if (useLocalCache) { return new PackageLoader(mUpdaterData, new DownloadCache(Strategy.ONLY_CACHE)); } else { return mUpdaterData.getPackageLoader(); } } /** * Overridden by the UI to respond to a request to refresh the tree viewer * when the input has changed. * The implementation must call {@link #setViewerInput()} somehow and will * also need to adjust the expand state of the tree items and/or update * some buttons or other state. */ abstract protected void refreshViewerInput(); /** * Invoked from {@link #refreshViewerInput()} to actually either set the * input of the tree viewer or refresh it if it's the same input * object. */ protected void setViewerInput() { List