/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. Jukefox is * distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.pancho3.view.overlays; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import java.util.TimerTask; import android.app.Activity; import android.content.res.Configuration; import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; import ch.ethz.dcg.jukefox.model.collection.BaseAlbum; import ch.ethz.dcg.jukefox.model.collection.BaseArtist; import ch.ethz.dcg.jukefox.model.collection.BaseSong; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.AbstractRecentAgent; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.AbstractRepetitionAgent; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.ArtistRepetitionAgent; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.IAgent; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.RandomAgent; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.SongRepetitionAgent; import ch.ethz.dcg.pancho3.R; import ch.ethz.dcg.pancho3.model.JukefoxApplication; public class AgentVotesToast { private static final String TAG = AgentVotesToast.class.getSimpleName(); private static Integer currentInfoBoxId = 0; private static View shownInfoBox = null; private final BaseSong<BaseArtist, BaseAlbum> song; private final Map<IAgent, Float> agentVotes; private final Activity parent; private View infoBox = null; private int infoBoxId; public AgentVotesToast(BaseSong<BaseArtist, BaseAlbum> song, Map<IAgent, Float> agentVotes, Activity parent) { this.agentVotes = agentVotes; this.parent = parent; this.song = song; } /** * Shows a toast with the top 3 agents (by weight) and their votes for a given song. Moreover their weight is shown * as a vertical bar. * * @param agentVotes * The agent votes * @param parent * The parent for the toast */ public void show() { if (agentVotes == null) { return; } // Create the infoBox (if not already done) initInfoBox(); synchronized (currentInfoBoxId) { infoBoxId = ++currentInfoBoxId; } // Show debug output of all ratings String votesMsg = String.format("The votes for [%s]: \n", song.toString()); for (Map.Entry<IAgent, Float> entry : agentVotes.entrySet()) { votesMsg += " " + getAgentTitle(entry.getKey()) + ": " + entry.getValue() + "\n"; } Log.d(TAG, votesMsg); // Show it! (in the ui-thread) JukefoxApplication.getHandler().post(new Runnable() { @Override public void run() { doShow(); } }); } private void initInfoBox() { synchronized (this) { if (infoBox != null) { return; } infoBox = View.inflate(parent, R.layout.agentsvote_toast, null); } /* // Fetch the agent weights List<Pair<IAgent, Double>> agentWeights = new ArrayList<Pair<IAgent, Double>>(); for (IAgent agent : agentVotes.keySet()) { double weight = AgentManager.getInstance().getAgentWeight(agent); agentWeights.add(new Pair<IAgent, Double>(agent, weight)); } // Sort the agent weights descending Collections.sort(agentWeights, new Comparator<Pair<IAgent, Double>>() { @Override public int compare(Pair<IAgent, Double> left, Pair<IAgent, Double> right) { return right.first.get-1 * left.second.compareTo(right.second); // -1 for descending } });*/ // Load the ui elements View bar = infoBox.findViewById(R.id.agentsvote_toast_bar); bar.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { dismiss(); } }); // Prepare the agent votes list (sort them by their votes; desc) List<Map.Entry<IAgent, Float>> agentVotesList = new ArrayList<Map.Entry<IAgent, Float>>(agentVotes.entrySet()); Collections.sort(agentVotesList, new Comparator<Map.Entry<IAgent, Float>>() { @Override public int compare(Entry<IAgent, Float> lhs, Entry<IAgent, Float> rhs) { float lhsV = Math.abs(lhs.getValue()); float rhsV = Math.abs(rhs.getValue()); return Float.compare(rhsV, lhsV); } }); assert agentVotesList.size() >= 3; // Make the display a little more interresting by add some agent constraints and use the agents with the most extreme votes boolean hasPositive = false; IAgent[] agents = new IAgent[3]; IAgent agentWithBestVote = null; float bestVote = -2.0f; int curAgent = 0; for (Map.Entry<IAgent, Float> entry : agentVotesList) { final IAgent agent = entry.getKey(); if (agent instanceof RandomAgent) { // Do not show random votes continue; } /*if (agent instanceof AbstractRepetitionAgent) { // Do not show anti repetition votes (They are most likely 0 anyway) continue; }*/ if (bestVote < entry.getValue()) { bestVote = entry.getValue(); agentWithBestVote = agent; } if (curAgent < agents.length) { if (entry.getValue() >= 0) { hasPositive = true; } agents[curAgent] = agent; ++curAgent; } } if (!hasPositive) { agents[agents.length - 1] = agentWithBestVote; // if none of the chosen agents voted positive, then add the agent with the best vote } loadAgent(R.id.agent1, agents[0]); loadAgent(R.id.agent2, agents[1]); loadAgent(R.id.agent3, agents[2]); } public void dismiss() { // Run doDismiss in the ui-thread JukefoxApplication.getHandler().post(new Runnable() { @Override public void run() { doDismiss(infoBox); } }); } /** * Adds the {@link #infoBox} to the parent. If {@link #shownInfoBox} is not null, we first dismiss that one. After * showning us, we set {@link #shownInfoBox} to {@link #infoBox}.<br/> * Attention: This must be run in the ui thread! */ private void doShow() { synchronized (currentInfoBoxId) { if (currentInfoBoxId != infoBoxId) { // A newer box is around return; } if (shownInfoBox == infoBox) { // We are already shown return; } // Dismiss the shown infoBox (if any) doDismiss(shownInfoBox); // Add some margins to make its display nicer if (parent.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { View tabs = parent.findViewById(R.id.tabsButtons); if (tabs != null) { View spacer = infoBox.findViewById(R.id.spacer_left); spacer.setLayoutParams(new LinearLayout.LayoutParams(tabs.getWidth() + 6, LayoutParams.MATCH_PARENT)); ((LinearLayout) infoBox).setGravity(Gravity.TOP); } } else { View playerConsole = parent.findViewById(R.id.playerConsole); if (playerConsole != null) { View spacer = infoBox.findViewById(R.id.spacer_bottom); spacer.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, playerConsole.getHeight() + 1)); ((LinearLayout) infoBox).setGravity(Gravity.BOTTOM); } } // Show us parent.addContentView(infoBox, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); shownInfoBox = infoBox; // Timer for automatic closing Timer t = new Timer(); t.schedule(new TimerTask() { @Override public void run() { dismiss(); } }, 8000); } } /** * Removes the given infoBox from the parent and sets {@link #shownInfoBox} to null if the given infoBox equals it.<br/> * Attention: This must be run in the ui thread! */ private void doDismiss(View infoBox) { if (infoBox == null) { return; } synchronized (currentInfoBoxId) { ViewGroup vg = (ViewGroup) infoBox.getParent(); if (vg != null) { vg.removeView(infoBox); } if (infoBox == shownInfoBox) { shownInfoBox = null; } } } /** * Loads the agent into the given ui element. * * @param agentLineId * The id of the top ui element * @param layout * The toast layout * @param agent * The agent */ private void loadAgent(int agentLineId, IAgent agent) { View top = infoBox.findViewById(agentLineId); // Display the agent name TextView text = (TextView) top.findViewById(R.id.agent_title); text.setText(getAgentTitle(agent)); // Display the vote SeekBar seekBar = (SeekBar) top.findViewById(R.id.agent_seekBar); float vote = agentVotes.get(agent); setAdjustedSeekPosition(seekBar, vote); } /** * Moves the given vote from [-1, 1] to [0, {@link SeekBar#getMax()}] <s>and uses more space for votes around 0 than * for bigger ones, since they are more likely</s>. It then assigns the calculated position to the seek bar. * * @param seekBar * @param vote */ private void setAdjustedSeekPosition(SeekBar seekBar, float vote) { //double importanceAdjusted = Math.signum(vote) * Math.pow(Math.abs(vote), 1 / 2.0d); double rangeAdjusted = (vote + 1.0d) / 2; // Move from [-1, 1] to [0, 1] seekBar.setProgress((int) (rangeAdjusted * seekBar.getMax())); } private String getAgentTitle(IAgent agent) { String mainTitle = getMainTitle(agent); String subTitle = getSubTitle(agent); if (!subTitle.isEmpty()) { mainTitle += String.format(" (%s)", subTitle); } return mainTitle; } private String getMainTitle(IAgent agent) { switch (agent.getAgentType()) { case Random: return parent.getString(R.string.agent_title_random_short); case Repetition: return parent.getString(R.string.agent_title_repetition_short); case Suggested: return parent.getString(R.string.agent_title_suggested_short); case Top: return parent.getString(R.string.agent_title_top_short); default: assert false; return ""; } } private String getSubTitle(IAgent agent) { switch (agent.getAgentType()) { case Random: return ""; case Repetition: AbstractRepetitionAgent repAgent = (AbstractRepetitionAgent) agent; if (repAgent.getRepetitionType().equals(ArtistRepetitionAgent.REPETITION_TYPE)) { return parent.getString(R.string.agent_subtitle_repetition_artist_short); } else if (repAgent.getRepetitionType().equals(SongRepetitionAgent.REPETITION_TYPE)) { return parent.getString(R.string.agent_subtitle_repetition_song_short); } else { assert false; return ""; } case Suggested: case Top: AbstractRecentAgent recAgent = (AbstractRecentAgent) agent; switch (recAgent.getTimeFilter()) { case DAY_OF_THE_WEEK: return parent.getString(R.string.agent_subtitle_recent_day_of_the_week); case HOUR_OF_THE_DAY: return parent.getString(R.string.agent_subtitle_recent_hour_of_the_day); case NONE: return parent.getString(R.string.agent_subtitle_recent_overall); case RECENTLY: return parent.getString(R.string.agent_subtitle_recent_recently); default: assert false; return ""; } default: assert false; return ""; } } }