/** * Get a new modhash by scraping and return it * * @param client * @return */ public static String doUpdateModhash(HttpClient client) { final Pattern MODHASH_PATTERN = Pattern.compile("modhash: '(.*?)'"); String modhash; HttpEntity entity = null; // The pattern to find modhash from HTML javascript area try { HttpGet httpget = new HttpGet(Constants.MODHASH_URL); HttpResponse response = client.execute(httpget); // For modhash, we don't care about the status, since the 404 page has the info we want. // status = response.getStatusLine().toString(); // if (!status.contains("OK")) // throw new HttpException(status); entity = response.getEntity(); BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent())); // modhash should appear within first 1200 chars char[] buffer = new char[1200]; in.read(buffer, 0, 1200); in.close(); String line = String.valueOf(buffer); entity.consumeContent(); if (StringUtils.isEmpty(line)) { throw new HttpException( "No content returned from doUpdateModhash GET to " + Constants.MODHASH_URL); } if (line.contains("USER_REQUIRED")) { throw new Exception("User session error: USER_REQUIRED"); } Matcher modhashMatcher = MODHASH_PATTERN.matcher(line); if (modhashMatcher.find()) { modhash = modhashMatcher.group(1); if (StringUtils.isEmpty(modhash)) { // Means user is not actually logged in. return null; } } else { throw new Exception("No modhash found at URL " + Constants.MODHASH_URL); } if (Constants.LOGGING) Common.logDLong(TAG, line); if (Constants.LOGGING) Log.d(TAG, "modhash: " + modhash); return modhash; } catch (Exception e) { if (entity != null) { try { entity.consumeContent(); } catch (Exception e2) { if (Constants.LOGGING) Log.e(TAG, "entity.consumeContent()", e); } } if (Constants.LOGGING) Log.e(TAG, "doUpdateModhash()", e); return null; } }
/** * This is an example of implementing an application service that will run in response to an alarm, * allowing us to move long duration work out of an intent receiver. * * @see AlarmService * @see AlarmService_Alarm */ public class EnvelopeService extends Service { NotificationManager mNM; private RedditSettings mSettings = new RedditSettings(); private HttpClient mClient = Common.getGzipHttpClient(); @Override public void onCreate() { mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); mSettings.loadRedditPreferences(this, mClient); new PeekEnvelopeServiceTask(this, mClient, mSettings.getMailNotificationStyle()).execute(); } private class PeekEnvelopeServiceTask extends PeekEnvelopeTask { public PeekEnvelopeServiceTask( Context context, HttpClient client, String mailNotificationStyle) { super(context, client, mailNotificationStyle); } @Override public void onPostExecute(Object count) { super.onPostExecute(count); EnvelopeService.this.stopSelf(); } } @Override public IBinder onBind(Intent intent) { return mBinder; } /** * This is the object that receives interactions from clients. See RemoteService for a more * complete example. */ private final IBinder mBinder = new Binder() { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { return super.onTransact(code, data, reply, flags); } }; public static void resetAlarm(Context context, long interval) { // Create an IntentSender that will launch our service, to be scheduled // with the alarm manager. PendingIntent alarmSender = PendingIntent.getService(context, 0, new Intent(context, EnvelopeService.class), 0); AlarmManager am = (AlarmManager) context.getSystemService(ALARM_SERVICE); am.cancel(alarmSender); if (interval != 0) am.setRepeating( AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), interval, alarmSender); } }
void resetUI(PickSubredditAdapter adapter) { setTheme(mSettings.getTheme()); setContentView(R.layout.pick_subreddit_view); registerForContextMenu(getListView()); synchronized (ADAPTER_LOCK) { if (adapter == null) { // Reset the list to be empty. mSubredditsList = new ArrayList<String>(); mSubredditsAdapter = new PickSubredditAdapter(this, mSubredditsList); } else { mSubredditsAdapter = adapter; } setListAdapter(mSubredditsAdapter); mSubredditsAdapter.mLoading = false; mSubredditsAdapter.notifyDataSetChanged(); // Just in case } Common.updateListDrawables(this, mSettings.getTheme()); // Set the EditText to do same thing as onListItemClick mEt = (EditText) findViewById(R.id.pick_subreddit_input); if (mEt != null) { mEt.setOnKeyListener( new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { returnSubreddit(mEt.getText().toString().trim()); return true; } return false; } }); mEt.setFocusableInTouchMode(true); } Button goButton = (Button) findViewById(R.id.pick_subreddit_button); if (goButton != null) { goButton.setOnClickListener( new OnClickListener() { public void onClick(View v) { returnSubreddit(mEt.getText().toString().trim()); } }); } getListView().requestFocus(); }
public static String checkResponseErrors(HttpResponse response, HttpEntity entity) { String status = response.getStatusLine().toString(); String line; if (!status.contains("OK")) { return "HTTP error. Status = " + status; } try { BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent())); line = in.readLine(); if (Constants.LOGGING) Common.logDLong(TAG, line); in.close(); } catch (IOException e) { if (Constants.LOGGING) Log.e(TAG, "IOException", e); return "Error reading retrieved data."; } if (StringUtils.isEmpty(line)) { return "API returned empty data."; } if (line.contains("WRONG_PASSWORD")) { return "Wrong password."; } if (line.contains("USER_REQUIRED")) { // The modhash probably expired return "Login expired."; } if (line.contains("SUBREDDIT_NOEXIST")) { return "That subreddit does not exist."; } if (line.contains("SUBREDDIT_NOTALLOWED")) { return "You are not allowed to post to that subreddit."; } return null; }
public static String checkIDResponse(HttpResponse response, HttpEntity entity) throws CaptchaException, Exception { // Group 1: fullname. Group 2: kind. Group 3: id36. final Pattern NEW_ID_PATTERN = Pattern.compile("\"id\": \"((.+?)_(.+?))\""); // Group 1: whole error. Group 2: the time part final Pattern RATELIMIT_RETRY_PATTERN = Pattern.compile("(you are trying to submit too fast. try again in (.+?)\\.)"); String status = response.getStatusLine().toString(); String line; if (!status.contains("OK")) { throw new Exception("HTTP error. Status = " + status); } try { BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent())); line = in.readLine(); if (Constants.LOGGING) Common.logDLong(TAG, line); in.close(); } catch (IOException e) { if (Constants.LOGGING) Log.e(TAG, "IOException", e); throw new Exception("Error reading retrieved data."); } if (StringUtils.isEmpty(line)) { throw new Exception("API returned empty data."); } if (line.contains("WRONG_PASSWORD")) { throw new Exception("Wrong password."); } if (line.contains("USER_REQUIRED")) { // The modhash probably expired throw new Exception("Login expired."); } if (line.contains("SUBREDDIT_NOEXIST")) { throw new Exception("That subreddit does not exist."); } if (line.contains("SUBREDDIT_NOTALLOWED")) { throw new Exception("You are not allowed to post to that subreddit."); } String newId; Matcher idMatcher = NEW_ID_PATTERN.matcher(line); if (idMatcher.find()) { newId = idMatcher.group(3); } else { if (line.contains("RATELIMIT")) { // Try to find the # of minutes using regex Matcher rateMatcher = RATELIMIT_RETRY_PATTERN.matcher(line); if (rateMatcher.find()) throw new Exception(rateMatcher.group(1)); else throw new Exception("you are trying to submit too fast. try again in a few minutes."); } if (line.contains("DELETED_LINK")) { throw new Exception("the link you are commenting on has been deleted"); } if (line.contains("BAD_CAPTCHA")) { throw new CaptchaException("Bad CAPTCHA. Try again."); } // No id returned by reply POST. return null; } // Getting here means success. return newId; }
public final class PickSubredditActivity extends ListActivity { private static final String TAG = "PickSubredditActivity"; // Group 1: inner private final Pattern MY_SUBREDDITS_OUTER = Pattern.compile("your front page reddits.*?<ul>(.*?)</ul>", Pattern.CASE_INSENSITIVE); // Group 3: subreddit name. Repeat the matcher.find() until it fails. private final Pattern MY_SUBREDDITS_INNER = Pattern.compile("<a(.*?)/r/(.*?)>(.+?)</a>"); private RedditSettings mSettings = new RedditSettings(); private HttpClient mClient = Common.getGzipHttpClient(); private PickSubredditAdapter mSubredditsAdapter; private ArrayList<String> mSubredditsList; private static final Object ADAPTER_LOCK = new Object(); private EditText mEt; private AsyncTask<?, ?, ?> mCurrentTask = null; private final Object mCurrentTaskLock = new Object(); public static final String[] DEFAULT_SUBREDDITS = { "reddit.com", "pics", "politics", "wtf", "funny", "technology", "askreddit", "science", "programming", "gaming", "worldnews", "comics", "offbeat", "videos", "environment", "iama", "business", "entertainment", "bestof", "economics", "marijuana", "todayilearned", "linux", "android" }; // A list of special subreddits that can be viewed, but cannot be used for submissions. They // inherit from the FakeSubreddit class // in the redditdev source, so we use the same naming here. Note: Should we add r/Random and // r/Friends? public static final String[] FAKE_SUBREDDITS = {Constants.FRONTPAGE_STRING, "all"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); CookieSyncManager.createInstance(getApplicationContext()); mSettings.loadRedditPreferences(this, mClient); setRequestedOrientation(mSettings.getRotation()); requestWindowFeature(Window.FEATURE_PROGRESS); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); resetUI(null); mSubredditsList = cacheSubredditsList(mSubredditsList); if (CollectionUtils.isEmpty(mSubredditsList)) restoreLastNonConfigurationInstance(); if (CollectionUtils.isEmpty(mSubredditsList)) { new DownloadRedditsTask().execute(); } else { addFakeSubredditsUnlessSuppressed(); resetUI(new PickSubredditAdapter(this, mSubredditsList)); } } @Override public void onResume() { super.onResume(); CookieSyncManager.getInstance().startSync(); } @Override public void onPause() { super.onPause(); CookieSyncManager.getInstance().stopSync(); } @Override public Object onRetainNonConfigurationInstance() { // Avoid having to re-download and re-parse the subreddits list // when rotating or opening keyboard. return mSubredditsList; } @SuppressWarnings("unchecked") private void restoreLastNonConfigurationInstance() { mSubredditsList = (ArrayList<String>) getLastNonConfigurationInstance(); } void resetUI(PickSubredditAdapter adapter) { setTheme(mSettings.getTheme()); setContentView(R.layout.pick_subreddit_view); registerForContextMenu(getListView()); synchronized (ADAPTER_LOCK) { if (adapter == null) { // Reset the list to be empty. mSubredditsList = new ArrayList<String>(); mSubredditsAdapter = new PickSubredditAdapter(this, mSubredditsList); } else { mSubredditsAdapter = adapter; } setListAdapter(mSubredditsAdapter); mSubredditsAdapter.mLoading = false; mSubredditsAdapter.notifyDataSetChanged(); // Just in case } Common.updateListDrawables(this, mSettings.getTheme()); // Set the EditText to do same thing as onListItemClick mEt = (EditText) findViewById(R.id.pick_subreddit_input); if (mEt != null) { mEt.setOnKeyListener( new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { returnSubreddit(mEt.getText().toString().trim()); return true; } return false; } }); mEt.setFocusableInTouchMode(true); } Button goButton = (Button) findViewById(R.id.pick_subreddit_button); if (goButton != null) { goButton.setOnClickListener( new OnClickListener() { public void onClick(View v) { returnSubreddit(mEt.getText().toString().trim()); } }); } getListView().requestFocus(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { String item = mSubredditsAdapter.getItem(position); returnSubreddit(item); } private void returnSubreddit(String subreddit) { Intent intent = new Intent(); intent.setData(Util.createSubredditUri(subreddit)); setResult(RESULT_OK, intent); finish(); } private void enableLoadingScreen() { if (Util.isLightTheme(mSettings.getTheme())) { setContentView(R.layout.loading_light); } else { setContentView(R.layout.loading_dark); } synchronized (ADAPTER_LOCK) { if (mSubredditsAdapter != null) mSubredditsAdapter.mLoading = true; } getWindow().setFeatureInt(Window.FEATURE_PROGRESS, 0); } private void disableLoadingScreen() { resetUI(mSubredditsAdapter); getWindow().setFeatureInt(Window.FEATURE_PROGRESS, 10000); } class DownloadRedditsTask extends AsyncTask<Void, Void, ArrayList<String>> { @Override public ArrayList<String> doInBackground(Void... voidz) { ArrayList<String> reddits = null; HttpEntity entity = null; try { reddits = cacheSubredditsList(reddits); if (reddits == null) { reddits = new ArrayList<String>(); HttpGet request = new HttpGet(Constants.REDDIT_BASE_URL + "/reddits"); // Set timeout to 15 seconds HttpParams params = request.getParams(); HttpConnectionParams.setConnectionTimeout(params, 15000); HttpConnectionParams.setSoTimeout(params, 15000); HttpResponse response = mClient.execute(request); entity = response.getEntity(); BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent())); String line = in.readLine(); in.close(); entity.consumeContent(); Matcher outer = MY_SUBREDDITS_OUTER.matcher(line); if (outer.find()) { Matcher inner = MY_SUBREDDITS_INNER.matcher(outer.group(1)); while (inner.find()) { reddits.add(inner.group(3)); } } else { return null; } if (Constants.LOGGING) Log.d(TAG, "new subreddit list size: " + reddits.size()); if (Constants.USE_SUBREDDITS_CACHE) { try { CacheInfo.setCachedSubredditList(getApplicationContext(), reddits); if (Constants.LOGGING) Log.d(TAG, "wrote subreddit list to cache:" + reddits); } catch (IOException e) { if (Constants.LOGGING) Log.e(TAG, "error on setCachedSubredditList", e); } } } return reddits; } catch (Exception e) { if (Constants.LOGGING) Log.e(TAG, "failed", e); if (entity != null) { try { entity.consumeContent(); } catch (Exception e2) { // Ignore. } } } return null; } @Override public void onPreExecute() { synchronized (mCurrentTaskLock) { if (mCurrentTask != null) { this.cancel(true); return; } mCurrentTask = this; } resetUI(null); enableLoadingScreen(); } @Override public void onPostExecute(ArrayList<String> reddits) { synchronized (mCurrentTaskLock) { mCurrentTask = null; } disableLoadingScreen(); if (reddits == null || reddits.size() == 0) { // Need to make a copy because Arrays.asList returns List backed by original array mSubredditsList = new ArrayList<String>(); mSubredditsList.addAll(Arrays.asList(DEFAULT_SUBREDDITS)); } else { mSubredditsList = reddits; } addFakeSubredditsUnlessSuppressed(); resetUI(new PickSubredditAdapter(PickSubredditActivity.this, mSubredditsList)); } } private void addFakeSubredditsUnlessSuppressed() { // Insert special reddits (front page, all) into subreddits list, unless suppressed by Intent // extras Bundle extras = getIntent().getExtras(); boolean addFakeSubreddits = false; if (extras != null) { boolean shouldHideFakeSubreddits = extras.getBoolean(Constants.EXTRA_HIDE_FAKE_SUBREDDITS_STRING, false); if (!shouldHideFakeSubreddits) { addFakeSubreddits = true; } } else { addFakeSubreddits = true; } if (addFakeSubreddits) { mSubredditsList.addAll(0, Arrays.asList(FAKE_SUBREDDITS)); } } private final class PickSubredditAdapter extends ArrayAdapter<String> { private LayoutInflater mInflater; private boolean mLoading = true; private int mFrequentSeparatorPos = ListView.INVALID_POSITION; public PickSubredditAdapter(Context context, List<String> objects) { super(context, 0, objects); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public boolean isEmpty() { if (mLoading) { // We don't want the empty state to show when loading. return false; } else { return super.isEmpty(); } } @Override public int getItemViewType(int position) { if (position == mFrequentSeparatorPos) { // We don't want the separator view to be recycled. return IGNORE_ITEM_VIEW_TYPE; } return super.getItemViewType(position); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; // Here view may be passed in for re-use, or we make a new one. if (convertView == null) { view = mInflater.inflate(android.R.layout.simple_list_item_1, null); } else { view = convertView; } TextView text = (TextView) view.findViewById(android.R.id.text1); text.setText(mSubredditsAdapter.getItem(position)); return view; } } protected Dialog onCreateDialog(int id) { Dialog dialog; ProgressDialog pdialog; switch (id) { // "Please wait" case Constants.DIALOG_LOADING_REDDITS_LIST: pdialog = new ProgressDialog(this); pdialog.setMessage("Loading your reddits..."); pdialog.setIndeterminate(true); pdialog.setCancelable(false); dialog = pdialog; break; default: throw new IllegalArgumentException("Unexpected dialog id " + id); } return dialog; } @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); final int[] myDialogs = { Constants.DIALOG_LOADING_REDDITS_LIST, }; for (int dialog : myDialogs) { try { dismissDialog(dialog); } catch (IllegalArgumentException e) { // Ignore. } } } protected ArrayList<String> cacheSubredditsList(ArrayList<String> reddits) { if (Constants.USE_SUBREDDITS_CACHE) { if (CacheInfo.checkFreshSubredditListCache(getApplicationContext())) { reddits = CacheInfo.getCachedSubredditList(getApplicationContext()); if (Constants.LOGGING) Log.d(TAG, "cached subreddit list:" + reddits); } } return reddits; } }