Java tutorial
/* * Copyright 2016 Google Inc. All Rights Reserved. * * 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.apps.forscience.whistlepunk.metadata; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SwitchCompat; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.PopupMenu; import android.widget.TextView; import com.google.android.apps.forscience.javalib.Success; import com.google.android.apps.forscience.whistlepunk.AccessibilityUtils; import com.google.android.apps.forscience.whistlepunk.AppSingleton; import com.google.android.apps.forscience.whistlepunk.DataController; import com.google.android.apps.forscience.whistlepunk.LoggingConsumer; import com.google.android.apps.forscience.whistlepunk.ProtoUtils; import com.google.android.apps.forscience.whistlepunk.R; import com.google.android.apps.forscience.whistlepunk.WhistlePunkApplication; import com.google.android.apps.forscience.whistlepunk.analytics.TrackerConstants; import com.google.android.apps.forscience.whistlepunk.data.GoosciSensorLayout; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * The fragment for displaying a list of triggers. */ public class TriggerListFragment extends Fragment { private static final String ARG_SENSOR_ID = "sensor_id"; private static final String ARG_EXPERIMENT_ID = "experiment_id"; private static final String ARG_LAYOUT_POSITION = "layout_position"; public static final String ARG_TRIGGER_ORDER = "trigger_order"; private static final String KEY_TRIGGER_ORDER = "trigger_order"; private static final String TAG = "TriggerListFragment"; private static String mSensorId; private String mExperimentId; private GoosciSensorLayout.SensorLayout mSensorLayout; private TriggerListAdapter mTriggerAdapter; private int mLayoutPosition; private ArrayList<String> mTriggerOrder; public static TriggerListFragment newInstance(String sensorId, String experimentId, int position, ArrayList<String> triggerOrder) { TriggerListFragment fragment = new TriggerListFragment(); Bundle args = new Bundle(); args.putString(ARG_SENSOR_ID, sensorId); args.putString(ARG_EXPERIMENT_ID, experimentId); args.putInt(ARG_LAYOUT_POSITION, position); args.putStringArrayList(ARG_TRIGGER_ORDER, triggerOrder); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSensorId = getArguments().getString(ARG_SENSOR_ID); mExperimentId = getArguments().getString(ARG_EXPERIMENT_ID); mLayoutPosition = getArguments().getInt(ARG_LAYOUT_POSITION); if (savedInstanceState != null) { mTriggerOrder = savedInstanceState.getStringArrayList(KEY_TRIGGER_ORDER); } else { mTriggerOrder = getArguments().getStringArrayList(ARG_TRIGGER_ORDER); } setHasOptionsMenu(true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); String sensorName = AppSingleton.getInstance(getActivity()).getSensorAppearanceProvider() .getAppearance(mSensorId).getName(getActivity()); actionBar.setTitle(getString(R.string.title_fragment_trigger_list, sensorName)); super.onCreateOptionsMenu(menu, inflater); } @Override public void onResume() { super.onResume(); getDataController().getSensorLayouts(mExperimentId, new LoggingConsumer<List<GoosciSensorLayout.SensorLayout>>(TAG, "get layout") { @Override public void success(List<GoosciSensorLayout.SensorLayout> value) { for (GoosciSensorLayout.SensorLayout layout : value) { if (TextUtils.equals(layout.sensorId, mSensorId)) { mSensorLayout = layout; } } } }); getDataController().getSensorTriggersForSensor(mSensorId, new LoggingConsumer<List<SensorTrigger>>(TAG, "get triggers for sensor") { @Override public void success(List<SensorTrigger> triggers) { Comparator<SensorTrigger> cp; if (mTriggerOrder != null) { // If this is not the first load, use the saved order to define a new // order, but insert new triggers at the top. cp = new Comparator<SensorTrigger>() { @Override public int compare(SensorTrigger lhs, SensorTrigger rhs) { int lhsIndex = mTriggerOrder.indexOf(lhs.getTriggerId()); int rhsIndex = mTriggerOrder.indexOf(rhs.getTriggerId()); if (lhsIndex == rhsIndex && lhsIndex == -1) { // If they are both not found, they are both new. return Long.compare(rhs.getLastUsed(), lhs.getLastUsed()); } return Integer.compare(lhsIndex, rhsIndex); } }; } else { // Only do this sort on the first load. cp = new Comparator<SensorTrigger>() { @Override public int compare(SensorTrigger lhs, SensorTrigger rhs) { boolean lhsIsActive = isTriggerActive(lhs); boolean rhsIsActive = isTriggerActive(rhs); if (lhsIsActive && !rhsIsActive) { return -1; } if (!lhsIsActive && rhsIsActive) { return 1; } return Long.compare(rhs.getLastUsed(), lhs.getLastUsed()); } }; } // Sort sensor triggers Collections.sort(triggers, cp); mTriggerAdapter.setSensorTriggers(triggers); } }); WhistlePunkApplication.getUsageTracker(getActivity()).trackScreenView(TrackerConstants.SCREEN_TRIGGER_LIST); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); saveTriggerOrder(); outState.putStringArrayList(KEY_TRIGGER_ORDER, mTriggerOrder); } private DataController getDataController() { return AppSingleton.getInstance(getActivity()).getDataController(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_trigger_list, parent, false); ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); } RecyclerView triggerList = (RecyclerView) view.findViewById(R.id.trigger_recycler_view); triggerList .setLayoutManager(new LinearLayoutManager(view.getContext(), LinearLayoutManager.VERTICAL, false)); mTriggerAdapter = new TriggerListAdapter(this); triggerList.setAdapter(mTriggerAdapter); FloatingActionButton addButton = (FloatingActionButton) view.findViewById(R.id.add_trigger_button); addButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { launchEditTriggerActivity(null); } }); return view; } private void launchEditTriggerActivity(SensorTrigger trigger) { Intent intent = new Intent(getActivity(), EditTriggerActivity.class); if (trigger != null) { intent.putExtra(EditTriggerActivity.EXTRA_TRIGGER_ID, trigger.getTriggerId()); intent.putExtra(EditTriggerActivity.EXTRA_TRIGGER_INFO_BLOB, ProtoUtils.makeBlob(trigger.getTriggerInformation())); } intent.putExtra(EditTriggerActivity.EXTRA_EXPERIMENT_ID, mExperimentId); intent.putExtra(EditTriggerActivity.EXTRA_SENSOR_ID, mSensorId); // Also send the Sensor Layout and the position so that this fragment can be recreated on // completion, and the order in which the triggers are shown so that the order does not // change when the user gets back. intent.putExtra(EditTriggerActivity.EXTRA_SENSOR_LAYOUT_BLOB, ProtoUtils.makeBlob(mSensorLayout)); intent.putExtra(TriggerListActivity.EXTRA_LAYOUT_POSITION, mLayoutPosition); saveTriggerOrder(); intent.putExtra(TriggerListActivity.EXTRA_TRIGGER_ORDER, mTriggerOrder); getActivity().startActivity(intent); } private void saveTriggerOrder() { mTriggerOrder = new ArrayList<>(); for (SensorTrigger trigger : mTriggerAdapter.mSensorTriggers) { mTriggerOrder.add(trigger.getTriggerId()); } } private void deleteTrigger(final SensorTrigger trigger, final int index) { final boolean isActive = isTriggerActive(trigger); final DataController dc = getDataController(); // Set up the undo snackbar. final Snackbar bar = AccessibilityUtils.makeSnackbar(getView(), getActivity().getResources().getString(R.string.sensor_trigger_deleted), Snackbar.LENGTH_SHORT); bar.setAction(R.string.snackbar_undo, new View.OnClickListener() { boolean mUndone = false; @Override public void onClick(View v) { if (mUndone) { return; } mUndone = true; dc.addSensorTrigger(trigger, mExperimentId, new LoggingConsumer<Success>(TAG, "re-add deleted trigger") { @Override public void success(Success value) { if (isActive) { // If it was active, re-add it to the Layout. TriggerHelper.addTriggerToLayoutActiveTriggers(mSensorLayout, trigger.getTriggerId()); dc.updateSensorLayout(mExperimentId, mLayoutPosition, mSensorLayout, new LoggingConsumer<Success>(TAG, "add trigger to layout") { @Override public void success(Success value) { mTriggerAdapter.addTriggerAtIndex(trigger, index); } }); } else { mTriggerAdapter.addTriggerAtIndex(trigger, index); } } }); } }); // Do the deletion, first by removing it from the layout and next by removing it // from the trigger database. TriggerHelper.removeTriggerFromLayoutActiveTriggers(mSensorLayout, trigger.getTriggerId()); dc.updateSensorLayout(mExperimentId, mLayoutPosition, mSensorLayout, new LoggingConsumer<Success>(TAG, "remove trigger from layout") { @Override public void success(Success value) { dc.deleteSensorTrigger(trigger, new LoggingConsumer<Success>(TAG, "delete trigger") { @Override public void success(Success value) { bar.show(); mTriggerAdapter.removeTrigger(trigger); } }); } }); } private boolean isTriggerActive(SensorTrigger trigger) { for (int i = 0; i < mSensorLayout.activeSensorTriggerIds.length; i++) { if (TextUtils.equals(trigger.getTriggerId(), mSensorLayout.activeSensorTriggerIds[i])) { return true; } } return false; } private void setSensorTriggerActive(SensorTrigger trigger, boolean isActive) { // Don't do the work if the state is already correct. if (isTriggerActive(trigger) == isActive) { return; } if (isActive) { TriggerHelper.addTriggerToLayoutActiveTriggers(mSensorLayout, trigger.getTriggerId()); } else { TriggerHelper.removeTriggerFromLayoutActiveTriggers(mSensorLayout, trigger.getTriggerId()); } getDataController().updateSensorLayout(mExperimentId, mLayoutPosition, mSensorLayout, LoggingConsumer.<Success>expectSuccess(TAG, "updating trigger active state")); // Note: Last used time is not updated when the trigger is activated / deactivated, and the // list should not be resorted at this time. } private static class TriggerListAdapter extends RecyclerView.Adapter<TriggerListAdapter.ViewHolder> { private static final int VIEW_TYPE_TRIGGER = 0; private static final int VIEW_TYPE_EMPTY = 1; List<SensorTrigger> mSensorTriggers = new ArrayList<>(); private final WeakReference<TriggerListFragment> mParentReference; public TriggerListAdapter(TriggerListFragment parent) { mParentReference = new WeakReference<TriggerListFragment>(parent); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); int viewId; if (viewType == VIEW_TYPE_EMPTY) { viewId = R.layout.empty_trigger_list; } else { viewId = R.layout.trigger_list_item; } return new ViewHolder(inflater.inflate(viewId, parent, false), viewType); } @Override public void onBindViewHolder(final ViewHolder holder, final int position) { if (getItemViewType(position) == VIEW_TYPE_EMPTY) { return; } final SensorTrigger trigger = mSensorTriggers.get(position); if (mParentReference.get() == null) { return; } holder.description .setText(TriggerHelper.buildDescription(trigger, mParentReference.get().getActivity())); holder.menuButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Context context = holder.menuButton.getContext(); PopupMenu popup = new PopupMenu(context, holder.menuButton); popup.getMenuInflater().inflate(R.menu.menu_sensor_trigger, popup.getMenu()); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.edit_trigger) { if (mParentReference.get() != null) { mParentReference.get().launchEditTriggerActivity(trigger); } return true; } else if (item.getItemId() == R.id.delete_trigger) { if (mParentReference.get() != null) { mParentReference.get().deleteTrigger(trigger, mSensorTriggers.indexOf(trigger)); } return true; } return false; } }); popup.show(); } }); holder.activationSwitch.setOnCheckedChangeListener(null); holder.activationSwitch.setChecked(mParentReference.get().isTriggerActive(trigger)); holder.activationSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mParentReference.get() != null) { mParentReference.get().setSensorTriggerActive(trigger, isChecked); } } }); } @Override public void onViewRecycled(TriggerListAdapter.ViewHolder holder) { int position = holder.getAdapterPosition(); if (position == RecyclerView.NO_POSITION) { return; } holder.activationSwitch.setOnCheckedChangeListener(null); holder.menuButton.setOnClickListener(null); } @Override public int getItemCount() { return mSensorTriggers.size() == 0 ? 1 : mSensorTriggers.size(); } @Override public int getItemViewType(int position) { if (position == 0 && mSensorTriggers.size() == 0) { return VIEW_TYPE_EMPTY; } return VIEW_TYPE_TRIGGER; } public void setSensorTriggers(List<SensorTrigger> sensorTriggers) { mSensorTriggers = sensorTriggers; notifyDataSetChanged(); } public void removeTrigger(SensorTrigger trigger) { if (mSensorTriggers.contains(trigger)) { mSensorTriggers.remove(trigger); } notifyDataSetChanged(); } public void addTriggerAtIndex(SensorTrigger trigger, int index) { mSensorTriggers.add(index, trigger); notifyDataSetChanged(); } public static class ViewHolder extends RecyclerView.ViewHolder { int mViewType; TextView description; ImageButton menuButton; SwitchCompat activationSwitch; public ViewHolder(View itemView, int viewType) { super(itemView); mViewType = viewType; if (mViewType == VIEW_TYPE_TRIGGER) { description = (TextView) itemView.findViewById(R.id.trigger_description); menuButton = (ImageButton) itemView.findViewById(R.id.btn_trigger_menu); activationSwitch = (SwitchCompat) itemView.findViewById(R.id.trigger_activation_switch); } } } } }