// Copyright 2009 Google Inc. // // 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.google.android.stardroid.layers; import com.google.android.stardroid.base.Maps; import com.google.android.stardroid.renderer.RendererController; import com.google.android.stardroid.renderer.RendererControllerBase; import com.google.android.stardroid.renderer.RendererController.AtomicSection; import com.google.android.stardroid.renderer.RendererControllerBase.RenderManager; import com.google.android.stardroid.renderer.RendererObjectManager.UpdateType; import com.google.android.stardroid.renderer.util.UpdateClosure; import com.google.android.stardroid.search.SearchResult; import com.google.android.stardroid.source.ImageSource; import com.google.android.stardroid.source.LineSource; import com.google.android.stardroid.source.PointSource; import com.google.android.stardroid.source.TextSource; import com.google.android.stardroid.util.Blog; import com.google.android.stardroid.util.MiscUtil; import android.content.res.Resources; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantLock; /** * Base implementation of the {@link Layer} interface. * * @author John Taylor * @author Brent Bryan */ public abstract class AbstractLayer implements Layer { private static final String TAG = MiscUtil.getTag(AbstractLayer.class); private final ReentrantLock renderMapLock = new ReentrantLock(); private final HashMap<Class<?>, RenderManager<?>> renderMap = Maps.newHashMap(); private final Resources resources; private RendererController renderer; public AbstractLayer(Resources resources) { this.resources = resources; } protected Resources getResources() { return resources; } @Override public void registerWithRenderer(RendererController rendererController) { this.renderMap.clear(); this.renderer = rendererController; updateLayerForControllerChange(); } protected abstract void updateLayerForControllerChange(); @Override public void setVisible(boolean visible) { renderMapLock.lock(); try { if (renderer == null) { Log.w(TAG, "Renderer not set - aborting " + this.getClass().getSimpleName()); return; } AtomicSection atomic = renderer.createAtomic(); for (Entry<Class<?>, RenderManager<?>> entry: renderMap.entrySet()) { entry.getValue().queueEnabled(visible, atomic); } renderer.queueAtomic(atomic); } finally { renderMapLock.unlock(); } } protected void addUpdateClosure(UpdateClosure closure) { if (renderer != null) { renderer.addUpdateClosure(closure); } } protected void removeUpdateClosure(UpdateClosure closure) { if (renderer != null) { renderer.removeUpdateCallback(closure); } } /** * Forces a redraw of this layer, clearing all of the information about this * layer in the renderer and repopulating it. */ protected abstract void redraw(); protected void redraw( final ArrayList<TextSource> textSources, final ArrayList<PointSource> pointSources, final ArrayList<LineSource> lineSources, final ArrayList<ImageSource> imageSources) { redraw(textSources, pointSources, lineSources, imageSources, EnumSet.of(UpdateType.Reset)); } /** * Updates the renderer (using the given {@link UpdateType}), with then given set of * UI elements. Depending on the value of {@link UpdateType}, current sources will * either have their state updated, or will be overwritten by the given set * of UI elements. */ protected void redraw( final ArrayList<TextSource> textSources, final ArrayList<PointSource> pointSources, final ArrayList<LineSource> lineSources, final ArrayList<ImageSource> imageSources, EnumSet<UpdateType> updateTypes) { // Log.d(TAG, getLayerName() + " Updating renderer: " + updateTypes); if (renderer == null) { Log.w(TAG, "Renderer not set - aborting: " + this.getClass().getSimpleName()); return; } renderMapLock.lock(); try { // Blog.d(this, "Redraw: " + updateTypes); AtomicSection atomic = renderer.createAtomic(); setSources(textSources, updateTypes, TextSource.class, atomic); setSources(pointSources, updateTypes, PointSource.class, atomic); setSources(lineSources, updateTypes, LineSource.class, atomic); setSources(imageSources, updateTypes, ImageSource.class, atomic); renderer.queueAtomic(atomic); } finally { renderMapLock.unlock(); } } /** * Sets the objects on the {@link RenderManager} to the given values, * creating (or disabling) the {@link RenderManager} if necessary. */ private <E> void setSources(ArrayList<E> sources, EnumSet<UpdateType> updateType, Class<E> clazz, AtomicSection atomic) { @SuppressWarnings("unchecked") RenderManager<E> manager = (RenderManager<E>) renderMap.get(clazz); if (sources == null || sources.isEmpty()) { if (manager != null) { // TODO(brent): we should really just disable this layer, but in a // manner that it will automatically be reenabled when appropriate. Blog.d(this, " " + clazz.getSimpleName()); manager.queueObjects(Collections.<E>emptyList(), updateType, atomic); } return; } if (manager == null) { manager = createRenderManager(clazz, atomic); renderMap.put(clazz, manager); } // Blog.d(this, " " + clazz.getSimpleName() + " " + sources.size()); manager.queueObjects(sources, updateType, atomic); } @SuppressWarnings("unchecked") <E> RenderManager<E> createRenderManager(Class<E> clazz, RendererControllerBase controller) { if (clazz.equals(ImageSource.class)) { return (RenderManager<E>) controller.createImageManager(getLayerId()); } else if (clazz.equals(TextSource.class)) { return (RenderManager<E>) controller.createLabelManager(getLayerId()); } else if (clazz.equals(LineSource.class)) { return (RenderManager<E>) controller.createLineManager(getLayerId()); } else if (clazz.equals(PointSource.class)) { return (RenderManager<E>) controller.createPointManager(getLayerId()); } throw new IllegalStateException("Unknown source type: " + clazz); } @Override public List<SearchResult> searchByObjectName(String name) { // By default, layers will return no search results. // Override this if the layer should be searchable. return Collections.emptyList(); } @Override public Set<String> getObjectNamesMatchingPrefix(String prefix) { // By default, layers will return no search results. // Override this if the layer should be searchable. return Collections.emptySet(); } /** * Provides a string ID to the internationalized name of this layer. */ // TODO(brent): could this be combined with getLayerId? Not sure - they // serve slightly different purposes. protected abstract int getLayerNameId(); @Override public String getPreferenceId() { return getPreferenceId(getLayerNameId()); } protected String getPreferenceId(int layerNameId) { return "source_provider." + layerNameId; } @Override public String getLayerName() { return getStringFromId(getLayerNameId()); } /** * Return an internationalized string from a string resource id. */ protected String getStringFromId(int resourceId) { return resources.getString(resourceId); } }