package de.bsd.zwitscher; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import android.app.ActionBar; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Bitmap; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.*; import de.bsd.zwitscher.account.Account; import de.bsd.zwitscher.account.AccountHolder; import de.bsd.zwitscher.helper.UserTagTokenizer; import de.bsd.zwitscher.helper.PicHelper; import de.bsd.zwitscher.helper.UrlHelper; import twitter4j.GeoLocation; import twitter4j.Status; import twitter4j.StatusUpdate; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import twitter4j.User; /** * This activity is called when the user wants to write a new tweet/dent. * This can be for a new message, a new direct message or also when replying * to an existing message. */ public class NewTweetActivity extends Activity implements LocationListener { private MultiAutoCompleteTextView edittext; private Status origStatus; private ProgressBar pg; private User toUser = null; private TextView charCountView; private String picturePath = null; private boolean pictureRemovable = false; private LocationManager locationManager; private Account account; private int picUrlCount; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.new_tweet); charCountView = (TextView) findViewById(R.id.CharCount); account = AccountHolder.getInstance(this).getAccount(); final ImageButton tweetButton = (ImageButton) findViewById(R.id.TweetButton); edittext = (MultiAutoCompleteTextView) findViewById(R.id.edittext); edittext.setSelected(true); // Set the MultiAutoCompleteTextView in a mode that allows tokenizing and // also the normal spell checker. // See http://stackoverflow.com/questions/4552292/edittext-and-multiautocompletetextview-suggestions/7761754#7761754 edittext.setRawInputType(InputType.TYPE_CLASS_TEXT |InputType.TYPE_TEXT_FLAG_CAP_SENTENCES |InputType.TYPE_TEXT_FLAG_AUTO_CORRECT |InputType.TYPE_TEXT_FLAG_MULTI_LINE); if (tweetButton!=null) tweetButton.setEnabled(false); Bundle bundle = getIntent().getExtras(); if (bundle!=null) { TextView textOben = (TextView) findViewById(R.id.textOben); String bundleText = bundle.getString(Intent.EXTRA_TEXT); String op = (String) bundle.get("op"); User directUser = (User) bundle.get("user"); // set when coming from user detail view if (op!=null) { origStatus = (Status) bundle.get("status"); if (origStatus!=null) { if (op.equals(getString(R.string.reply))) { Set<String> hashTags = getHashTags(origStatus.getText()); textOben.setText(origStatus.getText()); StringBuilder builder = new StringBuilder(); builder.append("@").append(origStatus.getUser().getScreenName()).append(" "); for (String hashTag : hashTags) { builder.append(hashTag).append(" "); } edittext.setText(builder.toString()); } else if (op.equals(getString(R.string.replyall))) { textOben.setText(origStatus.getText()); String oText = origStatus.getText(); StringBuilder sb = new StringBuilder(); sb.append("@"); sb.append(origStatus.getUser().getScreenName()).append(" "); findUsers (sb,oText); Set<String> hashTags = getHashTags(origStatus.getText()); for (String hashTag : hashTags) { sb.append(hashTag).append(" "); } edittext.setText(sb.toString()); } else if (op.equals(getString(R.string.classicretweet))) { textOben.setText(origStatus.getText()); String msg = "RT @" + origStatus.getUser().getScreenName() + " "; msg = msg + origStatus.getText(); edittext.setText(msg); // limit to 140 chars is done by the edittext via maxLength attribute } else if (op.equals(getString(R.string.direct))) { if (directUser!=null) toUser = directUser; else if (origStatus!=null) { toUser = origStatus.getUser(); } String s = getString(R.string.send_direct_to); textOben.setText(s + " "+ toUser.getScreenName()); } } else { // New tweet as reply to a direct message if (op.equals(getString(R.string.direct))) { if (directUser!=null) toUser = directUser; String s = getString(R.string.send_direct_to); textOben.setText(s + " "+ toUser.getScreenName()); } } } else { // OP is null -> new tweet if (bundleText!=null) edittext.setText(bundleText); else if (bundle.get(Intent.EXTRA_STREAM)!=null) { Uri externalUmageUri = (Uri) bundle.get(Intent.EXTRA_STREAM); // THis is a content:// uri. // decode the picture location picturePath = getPath(externalUmageUri); Toast.makeText(this,R.string.picture_attached,Toast.LENGTH_SHORT).show(); } } } edittext.setSelection(edittext.getText().length()); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); boolean locationEnabled = preferences.getBoolean("location",false); CheckBox box = (CheckBox) findViewById(R.id.GeoCheckBox); if (locationEnabled) { box.setEnabled(true); box.setChecked(true); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,1,1,this); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1,1,this); } // Add a listener to count the text length. edittext.addTextChangedListener(new TextWatcher() { public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { // Not needed } public void afterTextChanged(Editable editable) { // Not needed } public void onTextChanged(CharSequence charSequence, int start, int before, int count) { int tlen = edittext.getText().length(); if (tweetButton!=null) { if (tlen >0 ) { tweetButton.setEnabled(true); } else tweetButton.setEnabled(false); } charCountView.setText(String.valueOf(140-tlen- picUrlCount)); // TODO if url detected 4 twitter, decrease by 20 chars } }); // Now set the adapter and Tokenizer for auto-completion of @user and #hashtag edittext.setThreshold(2); // default seems to be 2 as well -> @ + 1 char Set<String> usernames = new HashSet<String>(); usernames.addAll(getUsernames(true)); usernames.add("#Zwitscher"); usernames.add("#Android"); usernames.add("#RHQ"); usernames.add("#JBoss"); usernames.add("#java"); usernames.add("@pilhuhn"); usernames.add("@RHQ_project"); System.out.println("hashes " + AccountHolder.getInstance(this).getHashTags().size()); System.out.println("users " + AccountHolder.getInstance(this).getUserNames().size()); usernames.addAll(AccountHolder.getInstance(this).getHashTags()); usernames.addAll(AccountHolder.getInstance(this).getUserNames()); ArrayAdapter<String> acAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line, usernames.toArray(new String[usernames.size()])); edittext.setAdapter(acAdapter); edittext.setTokenizer(new UserTagTokenizer()); } private Set<String> getHashTags(String text) { String[] tokens = text.split(" "); Set<String> tags = new HashSet<String>(); for (String token : tokens) { if (token.startsWith("#")) tags.add(token); } return tags; } @SuppressWarnings("unused") public void clear(View v) { edittext.setText(""); } @SuppressWarnings("unused") public void shortenUrls(View v) { String text = edittext.getText().toString(); Map<Integer,String> urls = new HashMap<Integer,String>(); String[] tokens = text.split(" "); for (int i = 0 ; i < tokens.length ; i++) { String token = tokens[i]; if (token.startsWith("http://") || token.startsWith("https://")) { urls.put(i,token); } } try { Map<Integer,String> replacements = new UrlShortenerTask(this,urls.size()).execute(urls).get(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < tokens.length ; i++) { if (replacements.containsKey(i)) builder.append(replacements.get(i)); else builder.append(tokens[i]); builder.append(" "); } String newText = builder.toString(); edittext.setText(newText); } catch (Exception e) { String tmp = getString(R.string.url_shortening_failed,e.getMessage()); Toast.makeText(this,tmp,Toast.LENGTH_LONG).show(); } } @SuppressWarnings("unused") public void finallySend(View v) { StatusUpdate up = new StatusUpdate(edittext.getText().toString()); // add location if enabled in preferences and checked on tweet CheckBox box = (CheckBox) findViewById(R.id.GeoCheckBox); boolean locationEnabled = box.isChecked(); if (locationEnabled) { Location location = getCurrentLocation(); if (location!=null) { GeoLocation geoLocation = new GeoLocation(location.getLatitude(),location.getLongitude()); up.setLocation(geoLocation); } } if (origStatus!=null) { up.setInReplyToStatusId(origStatus.getId()); } if (toUser==null) tweet(up); else direct(toUser,edittext.getText().toString()); origStatus=null; switchOffLocationUpdates(); finish(); } private Location getCurrentLocation() { LocationManager locMngr = (LocationManager) getSystemService(Context.LOCATION_SERVICE); Location currLoc = locMngr.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (currLoc == null) { currLoc = locMngr.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } return currLoc; } /** Extract the @users from the passed oText and put them into sb * TODO optimize * @param sb Builder to populate * @param oText Input text to analyze */ private void findUsers(StringBuilder sb, String oText) { if (!oText.contains("@")) return; String txt = oText; while (txt.length()>0) { int j = txt.indexOf("@"); if (j<0) return; txt = txt.substring(j); int k = txt.indexOf(" "); if (k<0) { // end sb.append(txt); return; } else { sb.append(txt.substring(0, k)); sb.append(" "); txt = txt.substring(k); } } } /** * Trigger an update (new tweet, reply ) * @param update Update to send */ void tweet(StatusUpdate update) { UpdateRequest request = new UpdateRequest(UpdateType.UPDATE); request.statusUpdate = update; if (picturePath!=null) request.picturePath = picturePath; request.someBool = pictureRemovable; Toast.makeText(this,R.string.trying_to_send,Toast.LENGTH_SHORT).show(); UpdateStatusService.sendUpdate(this,account,request); } /** * Trigger a direct message to the given user. * @param toUser user to send a direct message to * @param msg message to send */ private void direct(User toUser, String msg) { UpdateRequest request = new UpdateRequest(UpdateType.DIRECT); request.message = msg; request.id = toUser.getId(); if (picturePath!=null) request.picturePath = picturePath; request.someBool = pictureRemovable; Toast.makeText(this,R.string.trying_to_send,Toast.LENGTH_SHORT).show(); UpdateStatusService.sendUpdate(this,account,request); } /** * Trigger a list of usernames to pick one from and to insert * into the tweet * @param v Button that was touched */ @SuppressWarnings("unused") public void selectUser(View v) { List<String> data = getUsernames(false); Intent intent = new Intent(this,MultiSelectListActivity.class); intent.putStringArrayListExtra("data", (ArrayList<String>) data); intent.putExtra("mode","single"); startActivityForResult(intent, 2); } private List<String> getUsernames(boolean shortForm) { TwitterHelper th = new TwitterHelper(this, account); List<User> users = th.getUsersFromDb(); List<String> data = new ArrayList<String>(users.size()); for (User user : users) { StringBuilder sb = new StringBuilder("@"); sb.append(user.getScreenName()); if (!shortForm) { sb.append(", "); sb.append(user.getName()); } data.add(sb.toString()); } return data; } /** * Called from the Back button to finish (abort) the activity * @param v Button that was touched */ @SuppressWarnings("unused") public void done(View v) { finish(); } /** * Trigger taking a picture, called from the camera button. * Actually we present a menu from which the user will be able to take a picture * or select one from the gallery of pictures taken * @param v button that was pressed */ @SuppressWarnings("unused") public void takePicture(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true); builder.setTitle(R.string.take_picture_via); builder.setItems(R.array.take_picture_items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialogInterface, int i) { Intent intent; switch (i) { case 0: // small image intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent, 1); break; case 1: // large image Uri tmpUri = Uri.fromFile(getFixedTempFile(NewTweetActivity.this)); intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT,tmpUri); startActivityForResult(intent, 3); break; case 2: // from gallery intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, 4); } } }); AlertDialog dialog = builder.create(); dialog.show(); } /** * Process the result when the picture has been taken. * @param requestCode Code of the started activity * @param resultCode Indicator of success * @param data Values passed from the started activity */ @Override public void onActivityResult(int requestCode, int resultCode,Intent data) { super.onActivityResult(requestCode, resultCode, data); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); String provider = preferences.getString("pictureService","yfrog"); pictureRemovable = false; // code 1 = take small picture if(requestCode==1 && resultCode==RESULT_OK) { Bitmap bitmap = (Bitmap) data.getExtras().get("data"); PicHelper picHelper = new PicHelper(); picturePath = picHelper.storeBitmap(bitmap, getTempFile(this), Bitmap.CompressFormat.JPEG, 100); // TODO adjust quality per network Log.d("NewTweetActivity.onActivityResult","path: " + picturePath); pictureRemovable = true; if (provider.equals("twitter")) Toast.makeText(this,R.string.picture_attached,Toast.LENGTH_SHORT).show(); else picUrlCount=21; } else if (requestCode==2 && resultCode==RESULT_OK) { // select user String item = (String) data.getExtras().get("data"); if (item.contains(", ")) { String user = item.substring(0,item.indexOf(", ")); edittext.append( user + " "); } } else if (requestCode==3 && resultCode==RESULT_OK) { // take large picture // large size image File file = getFixedTempFile(this); File newPath = getTempFile(this); boolean success = file.renameTo(newPath); if (success) picturePath = newPath.getAbsolutePath(); else picturePath = file.getAbsolutePath(); pictureRemovable = true; Toast.makeText(this,R.string.picture_attached,Toast.LENGTH_SHORT).show(); if(!provider.equals("twitter")) picUrlCount=21; } else if (requestCode==4 && resultCode==RESULT_OK) { // picture from gallery // image from gallery Uri selectedImageUri = data.getData(); picturePath = getPath(selectedImageUri); Toast.makeText(this,R.string.picture_attached,Toast.LENGTH_SHORT).show(); if(!provider.equals("twitter")) picUrlCount=21; } } protected void onPause() { super.onPause(); switchOffLocationUpdates(); } private void switchOffLocationUpdates() { if (locationManager!=null) { locationManager.removeUpdates(this); } } public void onLocationChanged(Location location) { } public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.new_tweet_menu,menu); ActionBar actionBar = this.getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); return true; } public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); break; case R.id.send: finallySend(null); break; case R.id.camera: takePicture(null); break; case R.id.clear: clear(null); break; case R.id.pickUser: selectUser(null); break; case R.id.shortenUrls: shortenUrls(null); break; default: Log.e("NewTweetActivity","Unknown menu item: " + item.toString()); } return super.onOptionsItemSelected(item); } /** * Get a temporary file with a fixed (=known in advance) file name * @param context activity context * @return a temp file in the external storage in a package-specific directory */ private File getFixedTempFile(Context context) { File path = new File( Environment.getExternalStorageDirectory(), context.getPackageName() ); if(!path.exists()) path.mkdir(); File tempFile; tempFile = new File(path,"image.tmp"); return tempFile; } /** * Get a temporary file with a unique file name * @param context activity context * @return a temp file in the external storage in a package-specific directory */ private File getTempFile(Context context){ File path = new File( Environment.getExternalStorageDirectory(), context.getPackageName() ); if(!path.exists()) path.mkdir(); File tempFile; try { tempFile = File.createTempFile("img_", ".jpg", path); } catch (IOException e) { e.printStackTrace(); // TODO: Customise this generated block tempFile = new File(path,"image.tmp"); } Log.d("NewTweetActivity.getTempFile",tempFile.getAbsolutePath()); return tempFile; } // Taken from http://stackoverflow.com/questions/2169649/open-an-image-in-androids-built-in-gallery-app-programmatically/2636538#2636538 // In the future use something like InputStream is = getContentResolver().openInputStream(Uri.parse(YOUR_URI_STRING))); String getPath(Uri uri) { String[] projection = { MediaStore.Images.Media.DATA }; Cursor cursor = managedQuery(uri, projection, null, null, null); int column_index = cursor .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); return cursor.getString(column_index); } private class UrlShortenerTask extends AsyncTask<Map<Integer,String>,Integer,Map<Integer,String>>{ private Context context; private int num; private ProgressDialog dialog; private UrlShortenerTask(Context context,int num) { this.context = context; this.num = num; } protected Map<Integer,String> doInBackground(Map<Integer,String>... maps) { Map<Integer,String> res = new HashMap<Integer, String>(maps[0].size()); int i=0; for (Map.Entry<Integer,String> entry: maps[0].entrySet()) { int id = entry.getKey(); String oldUrl = entry.getValue(); String shortUrl = UrlHelper.shortenUrl(oldUrl); res.put(id,shortUrl); i++; publishProgress(i); } return res; } protected void onPreExecute() { dialog = new ProgressDialog(context); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.show(); } protected void onPostExecute(Map<Integer, String> integerStringMap) { if (dialog!=null) dialog.cancel(); } protected void onProgressUpdate(Integer... values) { int val = values[0]*10000/num; dialog.setProgress(val); } } }