Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1413)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java

Issue 2367373003: [Android] Allow setting recently visited search engines as default search engine (Closed)
Patch Set: Update based on Ian's comments and fix bugs caused by test cases. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.preferences; 5 package org.chromium.chrome.browser.preferences;
6 6
7 import android.content.Context; 7 import android.content.Context;
8 import android.content.Intent; 8 import android.content.Intent;
9 import android.content.SharedPreferences; 9 import android.content.SharedPreferences;
10 import android.content.res.Resources; 10 import android.content.res.Resources;
11 import android.os.Build; 11 import android.os.Build;
12 import android.os.Bundle; 12 import android.os.Bundle;
13 import android.text.SpannableString; 13 import android.text.SpannableString;
14 import android.text.style.ForegroundColorSpan; 14 import android.text.style.ForegroundColorSpan;
15 import android.view.LayoutInflater; 15 import android.view.LayoutInflater;
16 import android.view.View; 16 import android.view.View;
17 import android.view.View.AccessibilityDelegate; 17 import android.view.View.AccessibilityDelegate;
18 import android.view.View.OnClickListener; 18 import android.view.View.OnClickListener;
19 import android.view.ViewGroup; 19 import android.view.ViewGroup;
20 import android.view.accessibility.AccessibilityEvent; 20 import android.view.accessibility.AccessibilityEvent;
21 import android.view.accessibility.AccessibilityNodeInfo; 21 import android.view.accessibility.AccessibilityNodeInfo;
22 import android.widget.BaseAdapter; 22 import android.widget.BaseAdapter;
23 import android.widget.RadioButton; 23 import android.widget.RadioButton;
24 import android.widget.TextView; 24 import android.widget.TextView;
25 25
26 import org.chromium.base.ApiCompatibilityUtils; 26 import org.chromium.base.ApiCompatibilityUtils;
27 import org.chromium.base.ContextUtils; 27 import org.chromium.base.ContextUtils;
28 import org.chromium.base.metrics.RecordUserAction;
28 import org.chromium.chrome.R; 29 import org.chromium.chrome.R;
30 import org.chromium.chrome.browser.locale.LocaleManager;
29 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; 31 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
30 import org.chromium.chrome.browser.preferences.website.ContentSetting; 32 import org.chromium.chrome.browser.preferences.website.ContentSetting;
31 import org.chromium.chrome.browser.preferences.website.GeolocationInfo; 33 import org.chromium.chrome.browser.preferences.website.GeolocationInfo;
32 import org.chromium.chrome.browser.preferences.website.SingleWebsitePreferences; 34 import org.chromium.chrome.browser.preferences.website.SingleWebsitePreferences;
33 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge; 35 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge;
34 import org.chromium.chrome.browser.search_engines.TemplateUrlService; 36 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
35 import org.chromium.chrome.browser.search_engines.TemplateUrlService.LoadListene r; 37 import org.chromium.chrome.browser.search_engines.TemplateUrlService.LoadListene r;
36 import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrl ; 38 import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrl ;
37 import org.chromium.components.location.LocationUtils; 39 import org.chromium.components.location.LocationUtils;
38 import org.chromium.ui.text.SpanApplier; 40 import org.chromium.ui.text.SpanApplier;
39 import org.chromium.ui.text.SpanApplier.SpanInfo; 41 import org.chromium.ui.text.SpanApplier.SpanInfo;
40 42
43 import java.util.ArrayList;
41 import java.util.List; 44 import java.util.List;
42 45
43 /** 46 /**
44 * A custom adapter for listing search engines. 47 * A custom adapter for listing search engines.
45 */ 48 */
46 public class SearchEngineAdapter extends BaseAdapter implements LoadListener, On ClickListener { 49 public class SearchEngineAdapter extends BaseAdapter implements LoadListener, On ClickListener {
47 /** 50 private static final int VIEW_TYPE_ITEM = 0;
48 * A callback for reporting progress to the owner. 51 private static final int VIEW_TYPE_DIVIDER = 1;
49 */
50 public interface SelectSearchEngineCallback {
51 /**
52 * Called when the search engine data has loaded and we've determined th e currently active
53 * one.
54 * @param name Provides the name of it (with a simplified URL in parenth esis).
55 */
56 void currentSearchEngineDetermined(int selectedIndex);
57 }
58 52
59 // The current context. 53 // The current context.
60 private Context mContext; 54 private Context mContext;
61 55
62 // The layout inflater to use for the custom views. 56 // The layout inflater to use for the custom views.
63 private LayoutInflater mLayoutInflater; 57 private LayoutInflater mLayoutInflater;
64 58
65 // The callback to use for notifying caller of progress. 59 // The list of prepopluated and default search engines.
66 private SelectSearchEngineCallback mCallback; 60 private List<TemplateUrl> mPrepopulatedSearchEngines = new ArrayList<Templat eUrl>();
gone 2016/10/31 22:31:28 Add newlines between the previous variable and the
ltian 2016/11/01 18:08:48 Done.
67 61 // The list of recently visited search engines.
68 // The list of available search engines. 62 private List<TemplateUrl> mRecentSearchEngines = new ArrayList<TemplateUrl>( );
69 private List<TemplateUrl> mSearchEngines; 63 // The position (index into mPrepopulatedSearchEngines) of the currently sel ected search engine.
70 // The position (index into mSearchEngines) of the currently selected search engine. Can be -1 64 // Can be -1 if current search engine is managed and set to something other than the
71 // if current search engine is managed and set to something other than the p re-populated values. 65 // pre-populated values.
72 private int mSelectedSearchEnginePosition = -1; 66 private int mSelectedSearchEnginePosition = -1;
73 67
74 // The position of the default search engine before user's action. 68 // The position of the default search engine before user's action.
75 private int mInitialEnginePosition = -1; 69 private int mInitialEnginePosition = -1;
76 70
77 /** 71 /**
78 * Construct a SearchEngineAdapter. 72 * Construct a SearchEngineAdapter.
79 * @param context The current context. 73 * @param context The current context.
80 * @param callback The callback to use to communicate back.
81 */ 74 */
82 public SearchEngineAdapter(Context context, SelectSearchEngineCallback callb ack) { 75 public SearchEngineAdapter(Context context) {
83 mContext = context; 76 mContext = context;
84 mLayoutInflater = (LayoutInflater) mContext.getSystemService( 77 mLayoutInflater = (LayoutInflater) mContext.getSystemService(
85 Context.LAYOUT_INFLATER_SERVICE); 78 Context.LAYOUT_INFLATER_SERVICE);
86 mCallback = callback;
87 79
88 initEntries(); 80 initEntries();
89 } 81 }
90 82
91 /**
92 * @return The index of the selected engine before user's action.
93 */
94 public int getInitialSearchEnginePosition() {
95 return mInitialEnginePosition;
96 }
97
98 // Used for testing. 83 // Used for testing.
99 84
100 String getValueForTesting() { 85 String getValueForTesting() {
101 return Integer.toString(mSelectedSearchEnginePosition); 86 return Integer.toString(mSelectedSearchEnginePosition);
102 } 87 }
103 88
104 void setValueForTesting(String value) { 89 String setValueForTesting(String value) {
105 searchEngineSelected(Integer.parseInt(value)); 90 return searchEngineSelected(Integer.parseInt(value));
91 }
92
93 String getKeywordForTesting(int index) {
94 return toKeyword(index);
106 } 95 }
107 96
108 /** 97 /**
109 * Initialize the search engine list. 98 * Initialize the search engine list.
110 */ 99 */
111 private void initEntries() { 100 private void initEntries() {
112 TemplateUrlService templateUrlService = TemplateUrlService.getInstance() ; 101 TemplateUrlService templateUrlService = TemplateUrlService.getInstance() ;
113 if (!templateUrlService.isLoaded()) { 102 if (!templateUrlService.isLoaded()) {
114 templateUrlService.registerLoadListener(this); 103 templateUrlService.registerLoadListener(this);
115 templateUrlService.load(); 104 templateUrlService.load();
116 return; // Flow continues in onTemplateUrlServiceLoaded below. 105 return; // Flow continues in onTemplateUrlServiceLoaded below.
117 } 106 }
118 107
119 // Fetch all the search engine info and the currently active one. 108 int defaultSearchEngineIndex = templateUrlService.getDefaultSearchEngine Index();
120 mSearchEngines = templateUrlService.getLocalizedSearchEngines(); 109 for (TemplateUrl templateUrl : templateUrlService.getSearchEngines()) {
121 int searchEngineIndex = templateUrlService.getDefaultSearchEngineIndex() ; 110 if (templateUrl.getType() == TemplateUrlService.TYPE_PREPOPULATED
122 // Convert the TemplateUrl index into an index into mSearchEngines. 111 || templateUrl.getType() == TemplateUrlService.TYPE_DEFAULT) {
112 mPrepopulatedSearchEngines.add(templateUrl);
113 } else {
114 mRecentSearchEngines.add(templateUrl);
115 }
116 }
117
118 // Convert the TemplateUrl index into an index of mSearchEngines.
123 mSelectedSearchEnginePosition = -1; 119 mSelectedSearchEnginePosition = -1;
124 for (int i = 0; i < mSearchEngines.size(); ++i) { 120 for (int i = 0; i < mPrepopulatedSearchEngines.size(); ++i) {
125 if (mSearchEngines.get(i).getIndex() == searchEngineIndex) { 121 if (mPrepopulatedSearchEngines.get(i).getIndex() == defaultSearchEng ineIndex) {
126 mSelectedSearchEnginePosition = i; 122 mSelectedSearchEnginePosition = i;
127 } 123 }
128 } 124 }
125
126 for (int i = 0; i < mRecentSearchEngines.size(); ++i) {
127 if (mRecentSearchEngines.get(i).getIndex() == defaultSearchEngineInd ex) {
128 // Add one to offset the title for recent search engine list
129 mSelectedSearchEnginePosition = i + mPrepopulatedSearchEngines.s ize() + 1;
130 }
131 }
132
129 mInitialEnginePosition = mSelectedSearchEnginePosition; 133 mInitialEnginePosition = mSelectedSearchEnginePosition;
130 134
131 // Report back what is selected. 135 TemplateUrlService.getInstance().setSearchEngine(toKeyword(mSelectedSear chEnginePosition));
132 mCallback.currentSearchEngineDetermined(toIndex(mSelectedSearchEnginePos ition));
133 } 136 }
134 137
135 private int toIndex(int position) { 138 private String toKeyword(int position) {
136 return mSearchEngines.get(position).getIndex(); 139 if (position < mPrepopulatedSearchEngines.size()) {
140 return mPrepopulatedSearchEngines.get(position).getKeyword();
141 } else {
142 position -= mPrepopulatedSearchEngines.size() + 1;
143 return mRecentSearchEngines.get(position).getKeyword();
144 }
137 } 145 }
138 146
139 // BaseAdapter: 147 // BaseAdapter:
140 148
141 @Override 149 @Override
142 public int getCount() { 150 public int getCount() {
143 return mSearchEngines == null ? 0 : mSearchEngines.size(); 151 return mPrepopulatedSearchEngines == null
152 ? 0
153 : mPrepopulatedSearchEngines.size() + mRecentSearchEngines.size( ) + 1;
144 } 154 }
145 155
146 @Override 156 @Override
147 public Object getItem(int pos) { 157 public Object getItem(int pos) {
148 TemplateUrl templateUrl = mSearchEngines.get(pos); 158 if (pos < mPrepopulatedSearchEngines.size()) {
149 return templateUrl.getShortName(); 159 return mPrepopulatedSearchEngines.get(pos);
160 } else if (pos > mPrepopulatedSearchEngines.size()) {
161 pos -= mPrepopulatedSearchEngines.size() + 1;
162 return mRecentSearchEngines.get(pos);
163 }
164 return null;
150 } 165 }
151 166
152 @Override 167 @Override
153 public long getItemId(int position) { 168 public long getItemId(int position) {
154 return position; 169 return position;
155 } 170 }
156 171
157 @Override 172 @Override
173 public int getItemViewType(int position) {
174 if (position == mPrepopulatedSearchEngines.size()) {
175 return VIEW_TYPE_DIVIDER;
176 } else {
177 return VIEW_TYPE_ITEM;
178 }
179 }
180
181 @Override
158 public View getView(int position, View convertView, ViewGroup parent) { 182 public View getView(int position, View convertView, ViewGroup parent) {
159 View view = convertView; 183 View view = convertView;
184 TemplateUrl templateUrl = (TemplateUrl) getItem(position);
185 int itemViewType = getItemViewType(position);
160 if (convertView == null) { 186 if (convertView == null) {
161 view = mLayoutInflater.inflate(R.layout.search_engine, null); 187 if (itemViewType == VIEW_TYPE_DIVIDER) {
188 view = mLayoutInflater.inflate(R.layout.search_engine_recent_tit le, null);
189 } else {
190 view = mLayoutInflater.inflate(R.layout.search_engine, null);
191 }
192 }
193 if (itemViewType == VIEW_TYPE_DIVIDER) {
194 view.setClickable(false);
Ian Wen 2016/10/28 22:17:39 BTW this can be done in xml, so you don't need to
ltian 2016/10/31 21:40:16 Done.
195 return view;
162 } 196 }
163 197
164 view.setOnClickListener(this); 198 view.setOnClickListener(this);
165 view.setTag(position); 199 view.setTag(position);
166 200
167 // TODO(finnur): There's a tinting bug in the AppCompat lib (see http:// crbug.com/474695), 201 // TODO(finnur): There's a tinting bug in the AppCompat lib (see http:// crbug.com/474695),
168 // which causes the first radiobox to always appear selected, even if it is not. It is being 202 // which causes the first radiobox to always appear selected, even if it is not. It is being
169 // addressed, but in the meantime we should use the native RadioButton i nstead. 203 // addressed, but in the meantime we should use the native RadioButton i nstead.
170 RadioButton radioButton = (RadioButton) view.findViewById(R.id.radiobutt on); 204 RadioButton radioButton = (RadioButton) view.findViewById(R.id.radiobutt on);
171 // On Lollipop this removes the redundant animation ring on selection bu t on older versions 205 // On Lollipop this removes the redundant animation ring on selection bu t on older versions
172 // it would cause the radio button to disappear. 206 // it would cause the radio button to disappear.
173 // TODO(finnur): Remove the encompassing if statement once we go back to using the AppCompat 207 // TODO(finnur): Remove the encompassing if statement once we go back to using the AppCompat
174 // control. 208 // control.
175 final boolean selected = position == mSelectedSearchEnginePosition; 209 final boolean selected = position == mSelectedSearchEnginePosition;
176 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 210 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
177 radioButton.setBackgroundResource(0); 211 radioButton.setBackgroundResource(0);
178 } 212 }
179 radioButton.setChecked(selected); 213 radioButton.setChecked(selected);
180 214
181 TextView description = (TextView) view.findViewById(R.id.description); 215 TextView description = (TextView) view.findViewById(R.id.description);
182 TemplateUrl templateUrl = mSearchEngines.get(position);
183 Resources resources = mContext.getResources(); 216 Resources resources = mContext.getResources();
184 description.setText(templateUrl.getShortName()); 217 description.setText(templateUrl.getShortName());
185 218
219 TextView url = (TextView) view.findViewById(R.id.url);
220 url.setText(templateUrl.getUrl());
221 if (templateUrl.getType() == TemplateUrlService.TYPE_PREPOPULATED
222 || templateUrl.getType() == TemplateUrlService.TYPE_DEFAULT
223 || templateUrl.getUrl().length() == 0) {
224 url.setVisibility(View.GONE);
225 }
226
186 // To improve the explore-by-touch experience, the radio button is hidde n from accessibility 227 // To improve the explore-by-touch experience, the radio button is hidde n from accessibility
187 // and instead, "checked" or "not checked" is read along with the search engine's name, e.g. 228 // and instead, "checked" or "not checked" is read along with the search engine's name, e.g.
188 // "google.com checked" or "google.com not checked". 229 // "google.com checked" or "google.com not checked".
189 radioButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILIT Y_NO); 230 radioButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILIT Y_NO);
190 description.setAccessibilityDelegate(new AccessibilityDelegate() { 231 description.setAccessibilityDelegate(new AccessibilityDelegate() {
191 @Override 232 @Override
192 public void onInitializeAccessibilityEvent(View host, AccessibilityE vent event) { 233 public void onInitializeAccessibilityEvent(View host, AccessibilityE vent event) {
193 super.onInitializeAccessibilityEvent(host, event); 234 super.onInitializeAccessibilityEvent(host, event);
194 event.setChecked(selected); 235 event.setChecked(selected);
195 } 236 }
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
240 281
241 @Override 282 @Override
242 public void onClick(View view) { 283 public void onClick(View view) {
243 if (view.getTag() == null) { 284 if (view.getTag() == null) {
244 onLocationLinkClicked(); 285 onLocationLinkClicked();
245 } else { 286 } else {
246 searchEngineSelected((int) view.getTag()); 287 searchEngineSelected((int) view.getTag());
247 } 288 }
248 } 289 }
249 290
250 private void searchEngineSelected(int position) { 291 private String searchEngineSelected(int position) {
251 // First clean up any automatically added permissions (if any) for the p reviously selected 292 // First clean up any automatically added permissions (if any) for the p reviously selected
252 // search engine. 293 // search engine.
253 SharedPreferences sharedPreferences = 294 SharedPreferences sharedPreferences =
254 ContextUtils.getAppSharedPreferences(); 295 ContextUtils.getAppSharedPreferences();
255 if (sharedPreferences.getBoolean(PrefServiceBridge.LOCATION_AUTO_ALLOWED , false)) { 296 if (sharedPreferences.getBoolean(PrefServiceBridge.LOCATION_AUTO_ALLOWED , false)) {
256 if (locationEnabled(mSelectedSearchEnginePosition, false)) { 297 if (locationEnabled(mSelectedSearchEnginePosition, false)) {
257 String url = TemplateUrlService.getInstance().getSearchEngineUrl FromTemplateUrl( 298 String url = TemplateUrlService.getInstance().getSearchEngineUrl FromTemplateUrl(
258 toIndex(mSelectedSearchEnginePosition)); 299 toKeyword(mSelectedSearchEnginePosition));
259 WebsitePreferenceBridge.nativeSetGeolocationSettingForOrigin( 300 WebsitePreferenceBridge.nativeSetGeolocationSettingForOrigin(
260 url, url, ContentSetting.DEFAULT.toInt(), false); 301 url, url, ContentSetting.DEFAULT.toInt(), false);
261 } 302 }
262 sharedPreferences.edit().remove(PrefServiceBridge.LOCATION_AUTO_ALLO WED).apply(); 303 sharedPreferences.edit().remove(PrefServiceBridge.LOCATION_AUTO_ALLO WED).apply();
263 } 304 }
264 305
265 // Record the change in search engine. 306 // Record the change in search engine.
266 mSelectedSearchEnginePosition = position; 307 mSelectedSearchEnginePosition = position;
267 308
268 // Report the change back. 309 String keyword = toKeyword(mSelectedSearchEnginePosition);
269 mCallback.currentSearchEngineDetermined(toIndex(mSelectedSearchEnginePos ition)); 310 TemplateUrlService.getInstance().setSearchEngine(keyword);
270 311
312 // If the user has manually set the default search engine, disable auto switching.
313 boolean manualSwitch = mSelectedSearchEnginePosition != mInitialEnginePo sition;
314 if (manualSwitch) {
315 RecordUserAction.record("SearchEngine_ManualChange");
316 LocaleManager.getInstance().setSearchEngineAutoSwitch(false);
317 }
271 notifyDataSetChanged(); 318 notifyDataSetChanged();
319 return keyword;
272 } 320 }
273 321
274 private void onLocationLinkClicked() { 322 private void onLocationLinkClicked() {
275 if (!LocationUtils.getInstance().isSystemLocationSettingEnabled()) { 323 if (!LocationUtils.getInstance().isSystemLocationSettingEnabled()) {
276 mContext.startActivity(LocationUtils.getInstance().getSystemLocation SettingsIntent()); 324 mContext.startActivity(LocationUtils.getInstance().getSystemLocation SettingsIntent());
277 } else { 325 } else {
278 Intent settingsIntent = PreferencesLauncher.createIntentForSettingsP age( 326 Intent settingsIntent = PreferencesLauncher.createIntentForSettingsP age(
279 mContext, SingleWebsitePreferences.class.getName()); 327 mContext, SingleWebsitePreferences.class.getName());
280 String url = TemplateUrlService.getInstance().getSearchEngineUrlFrom TemplateUrl( 328 String url = TemplateUrlService.getInstance().getSearchEngineUrlFrom TemplateUrl(
281 toIndex(mSelectedSearchEnginePosition)); 329 toKeyword(mSelectedSearchEnginePosition));
282 Bundle fragmentArgs = SingleWebsitePreferences.createFragmentArgsFor Site(url); 330 Bundle fragmentArgs = SingleWebsitePreferences.createFragmentArgsFor Site(url);
283 fragmentArgs.putBoolean(SingleWebsitePreferences.EXTRA_LOCATION, 331 fragmentArgs.putBoolean(SingleWebsitePreferences.EXTRA_LOCATION,
284 locationEnabled(mSelectedSearchEnginePosition, true)); 332 locationEnabled(mSelectedSearchEnginePosition, true));
285 settingsIntent.putExtra(Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS, f ragmentArgs); 333 settingsIntent.putExtra(Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS, f ragmentArgs);
286 mContext.startActivity(settingsIntent); 334 mContext.startActivity(settingsIntent);
287 } 335 }
288 } 336 }
289 337
290 private boolean locationEnabled(int position, boolean checkGeoHeader) { 338 private boolean locationEnabled(int position, boolean checkGeoHeader) {
291 if (position == -1) return false; 339 if (position == -1) return false;
292 340
293 String url = TemplateUrlService.getInstance().getSearchEngineUrlFromTemp lateUrl( 341 String url = TemplateUrlService.getInstance().getSearchEngineUrlFromTemp lateUrl(
294 toIndex(position)); 342 toKeyword(position));
295 GeolocationInfo locationSettings = new GeolocationInfo(url, null, false) ; 343 GeolocationInfo locationSettings = new GeolocationInfo(url, null, false) ;
296 ContentSetting locationPermission = locationSettings.getContentSetting() ; 344 ContentSetting locationPermission = locationSettings.getContentSetting() ;
297 // Handle the case where the geoHeader being sent when no permission has been specified. 345 // Handle the case where the geoHeader being sent when no permission has been specified.
298 if (locationPermission == ContentSetting.ASK && checkGeoHeader) { 346 if (locationPermission == ContentSetting.ASK && checkGeoHeader) {
299 return GeolocationHeader.isGeoHeaderEnabledForUrl(mContext, url, fal se); 347 return GeolocationHeader.isGeoHeaderEnabledForUrl(mContext, url, fal se);
300 } 348 }
301 return locationPermission == ContentSetting.ALLOW; 349 return locationPermission == ContentSetting.ALLOW;
302 } 350 }
303 } 351 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698