1 package org.skia.viewer; 2 3 import android.text.Editable; 4 import android.text.TextWatcher; 5 import android.view.LayoutInflater; 6 import android.view.SurfaceView; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.AdapterView; 10 import android.widget.BaseAdapter; 11 import android.widget.CompoundButton; 12 import android.widget.EditText; 13 import android.widget.LinearLayout; 14 import android.widget.Spinner; 15 import android.widget.Switch; 16 import android.widget.TextView; 17 18 import org.json.JSONArray; 19 import org.json.JSONException; 20 import org.json.JSONObject; 21 22 import java.util.ArrayList; 23 24 /* 25 The navigation drawer requires ListView, so we implemented this BaseAdapter for that ListView. 26 However, the ListView does not provide good support for updating just a single child view. 27 For example, a frequently changed child view such as FPS state will reset the spinner of 28 all other child views; although I didn't change other child views and directly return 29 the convertView in BaseAdapter.getView(int position, View convertView, ViewGroup parent). 30 31 Therefore, our adapter only returns one LinearLayout for the ListView. 32 Within that LinearLayout, we maintain views ourselves so we can efficiently update its children. 33 */ 34 public class StateAdapter extends BaseAdapter implements AdapterView.OnItemSelectedListener { 35 private static final String NAME = "name"; 36 private static final String VALUE = "value"; 37 private static final String OPTIONS = "options"; 38 private static final String BACKEND_STATE_NAME = "Backend"; 39 private static final String REFRESH_STATE_NAME = "Refresh"; 40 private static final String ON = "ON"; 41 private static final String OFF = "OFF"; 42 private static final int FILTER_LENGTH = 20; 43 44 private ViewerActivity mViewerActivity; 45 private LinearLayout mLayout; 46 private JSONArray mStateJson; 47 StateAdapter(ViewerActivity viewerActivity)48 public StateAdapter(ViewerActivity viewerActivity) { 49 mViewerActivity = viewerActivity; 50 try { 51 mStateJson = new JSONArray("[{\"name\": \"Please\", " + 52 "\"value\": \"Initialize\", \"options\": []}]"); 53 } catch (JSONException e) { 54 e.printStackTrace(); 55 } 56 } 57 setState(String stateJson)58 public void setState(String stateJson) { 59 try { 60 mStateJson = new JSONArray(stateJson); 61 if (mLayout != null) { 62 updateDrawer(); 63 } else { 64 notifyDataSetChanged(); 65 } 66 } catch (JSONException e) { 67 e.printStackTrace(); 68 } 69 } 70 71 // The first list item is the mLayout that contains a list of state items 72 @Override getCount()73 public int getCount() { 74 return 1; 75 } 76 77 @Override getItem(int position)78 public Object getItem(int position) { 79 return null; 80 } 81 82 @Override getItemId(int position)83 public long getItemId(int position) { 84 return 0; 85 } 86 87 @Override getView(int position, View convertView, ViewGroup parent)88 public View getView(int position, View convertView, ViewGroup parent) { 89 switch (position) { 90 case 0: { 91 if (mLayout == null) { 92 mLayout = new LinearLayout(mViewerActivity); 93 mLayout.setOrientation(LinearLayout.VERTICAL); 94 updateDrawer(); 95 } 96 return mLayout; 97 } 98 default: { 99 return null; 100 } 101 } 102 } 103 populateView(JSONObject item, View view)104 private void populateView(JSONObject item, View view) throws JSONException { 105 LinearLayout itemView = (LinearLayout) view; 106 TextView nameText = (TextView) itemView.findViewById(R.id.nameText); 107 TextView valueText = (TextView) itemView.findViewById(R.id.valueText); 108 Spinner optionSpinner = (Spinner) itemView.findViewById(R.id.optionSpinner); 109 110 String value = item.getString(VALUE); 111 itemView.setTag(item.toString()); // To save unnecessary view update 112 itemView.setTag(R.integer.value_tag_key, value); 113 114 nameText.setText(item.getString(NAME)); 115 116 JSONArray options = item.getJSONArray(OPTIONS); 117 if (options.length() == 0) { 118 valueText.setText(value); 119 valueText.setVisibility(View.VISIBLE); 120 optionSpinner.setVisibility(View.GONE); 121 } else { 122 ArrayList<String> optionList = new ArrayList<>(); 123 String[] optionStrings = new String[options.length()]; 124 for (int j = 0; j < options.length(); j++) { 125 optionList.add(options.getString(j)); 126 } 127 final OptionAdapter adapter = new OptionAdapter(mViewerActivity, 128 android.R.layout.simple_spinner_dropdown_item, optionList, optionSpinner); 129 adapter.setCurrentOption(value); 130 optionSpinner.setAdapter(adapter); 131 if (optionStrings.length >= FILTER_LENGTH) { 132 View existingView = itemView.getChildAt(1); 133 if (!(existingView instanceof EditText)) { 134 EditText filterText = new EditText(mViewerActivity); 135 filterText.setHint("Filter"); 136 itemView.addView(filterText, 1); 137 filterText.addTextChangedListener(new TextWatcher() { 138 @Override 139 public void beforeTextChanged(CharSequence s, int start, int cnt, 140 int after) { 141 } 142 143 @Override 144 public void onTextChanged(CharSequence s, int start, int before, int cnt) { 145 } 146 147 @Override 148 public void afterTextChanged(Editable s) { 149 adapter.getFilter().filter(s.toString()); 150 } 151 }); 152 } 153 } 154 optionSpinner.setSelection(optionList.indexOf(value)); 155 optionSpinner.setOnItemSelectedListener(this); 156 optionSpinner.setVisibility(View.VISIBLE); 157 valueText.setVisibility(View.GONE); 158 } 159 } inflateItemView(JSONObject item)160 private View inflateItemView(JSONObject item) throws JSONException { 161 LinearLayout itemView = (LinearLayout) 162 LayoutInflater.from(mViewerActivity).inflate(R.layout.state_item, null); 163 populateView(item, itemView); 164 return itemView; 165 } 166 updateDrawer()167 private void updateDrawer() { 168 try { 169 if (mStateJson.length() < mLayout.getChildCount()) { 170 mLayout.removeViews( 171 mStateJson.length(), mLayout.getChildCount() - mStateJson.length()); 172 } 173 for (int i = 0; i < mStateJson.length(); i++) { 174 JSONObject stateObject = mStateJson.getJSONObject(i); 175 View childView = mLayout.getChildAt(i); 176 if (childView != null) { 177 if (stateObject.toString().equals(childView.getTag())) { 178 continue; // No update, reuse the old view and skip the remaining step 179 } else { 180 populateView(stateObject, childView); 181 } 182 } else { 183 mLayout.addView(inflateItemView(stateObject), i); 184 } 185 } 186 } catch (JSONException e) { 187 e.printStackTrace(); 188 } 189 } 190 191 @Override onItemSelected(AdapterView<?> parent, View view, int position, long id)192 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 193 if (view == null) { 194 return; 195 } 196 View stateItem = (View) parent.getParent(); 197 String stateName = ((TextView) stateItem.findViewById(R.id.nameText)).getText().toString(); 198 String stateValue = ((TextView) view).getText().toString(); 199 if (!stateValue.equals(stateItem.getTag(R.integer.value_tag_key))) { 200 stateItem.setTag(null); // Reset the tag to let updateDrawer update this item view. 201 mViewerActivity.onStateChanged(stateName, stateValue); 202 } 203 204 // Due to the current Android limitation, we're required to recreate the SurfaceView for 205 // switching to/from the Raster backend. 206 // (Although we can switch between GPU backend without recreating the SurfaceView.) 207 final Object oldValue = stateItem.getTag(R.integer.value_tag_key); 208 if (stateName.equals(BACKEND_STATE_NAME) 209 && oldValue != null && !stateValue.equals(oldValue)) { 210 LinearLayout mainLayout = (LinearLayout) mViewerActivity.findViewById(R.id.mainLayout); 211 mainLayout.removeAllViews(); 212 SurfaceView surfaceView = new SurfaceView(mViewerActivity); 213 surfaceView.setId(R.id.surfaceView); 214 surfaceView.getHolder().addCallback(mViewerActivity); 215 surfaceView.setOnTouchListener(mViewerActivity); 216 mainLayout.addView(surfaceView); 217 } 218 } 219 220 @Override onNothingSelected(AdapterView<?> parent)221 public void onNothingSelected(AdapterView<?> parent) { 222 // do nothing 223 } 224 } 225