Simplify the process to retrieve remote picture and content (Mastalab logic).

This commit is contained in:
tom79 2017-09-02 10:13:40 +02:00
parent 21a86957de
commit 831eed8799
7 changed files with 165 additions and 222 deletions

View File

@ -17,11 +17,14 @@ package fr.gouv.etalab.mastodon.activities;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.net.Uri;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
@ -40,7 +43,6 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.Menu;
@ -50,6 +52,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import android.webkit.URLUtil;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
@ -108,7 +111,6 @@ import fr.gouv.etalab.mastodon.client.PatchBaseImageDownloader;
import fr.gouv.etalab.mastodon.drawers.AccountsSearchAdapter;
import fr.gouv.etalab.mastodon.drawers.DraftsListAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.helper.ParserUtils;
import fr.gouv.etalab.mastodon.interfaces.OnPostStatusActionInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAttachmentInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveSearcAccountshInterface;
@ -118,7 +120,6 @@ import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import fr.gouv.etalab.mastodon.sqlite.StatusStoredDAO;
import mastodon.etalab.gouv.fr.mastodon.R;
import static fr.gouv.etalab.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE;
import static fr.gouv.etalab.mastodon.helper.Helper.HOME_TIMELINE_INTENT;
import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION;
import static fr.gouv.etalab.mastodon.helper.Helper.changeDrawableColor;
@ -128,7 +129,7 @@ import static fr.gouv.etalab.mastodon.helper.Helper.changeDrawableColor;
* Toot activity class
*/
public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAccountshInterface, OnRetrieveAttachmentInterface, OnPostStatusActionInterface, ParserUtils.ParserListener {
public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAccountshInterface, OnRetrieveAttachmentInterface, OnPostStatusActionInterface {
private String visibility;
@ -146,7 +147,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
private EditText toot_cw_content;
private LinearLayout toot_reply_content_container;
private Status tootReply = null;
private String sharedContent, sharedSubject, sharedStream;
private String sharedContent, sharedSubject;
private CheckBox toot_sensitive;
public long currentToId;
private long restored;
@ -159,6 +160,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
private int currentCursorPosition, searchLength;
private TextView toot_space_left;
private final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 754;
private BroadcastReceiver receive_picture;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -251,7 +253,6 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
tootReply = b.getParcelable("tootReply");
sharedContent = b.getString("sharedContent", null);
sharedSubject = b.getString("sharedSubject", null);
sharedStream = b.getString("sharedStream", null);
// ACTION_SEND route
if (b.getInt("uriNumber", 0) == 1) {
@ -311,43 +312,45 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
if( sharedContent != null ){ //Shared content
// ParserUtils is a class I borrowed from Tusky.
final ParserUtils parser = new ParserUtils(this);
parser.putInClipboardManager(TootActivity.this, sharedContent);
String urlString = parser.getPastedURLText(TootActivity.this);
if( sharedSubject != null){
sharedContent = sharedSubject + "\n\n" + sharedContent;
}
if( sharedStream != null){
AsyncHttpClient client = new AsyncHttpClient();
String[] allowedTypes = new String[] { "image/png" };
client.get(url, new BinaryHttpResponseHandler(allowedTypes) {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) {
OutputStream f;
try {
f = new FileOutputStream(getCacheDir());
picture_scrollview.setVisibility(View.VISIBLE);
ByteArrayInputStream bis = new ByteArrayInputStream(binaryData);
loading_picture.setVisibility(View.VISIBLE);
toot_picture.setEnabled(false);
new UploadActionAsyncTask(getApplicationContext(),bis,TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
f.write(binaryData); //your bytes
f.close();
} catch (IOException e) {
e.printStackTrace();
}
receive_picture = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String url = intent.getStringExtra("pictureURL");
if( url != null){
AsyncHttpClient client = new AsyncHttpClient();
String[] allowedTypes = new String[] { "image/png","image/jpeg" };
client.get(url, new BinaryHttpResponseHandler(allowedTypes) {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] binaryData) {
OutputStream f;
try {
f = new FileOutputStream(getCacheDir() + URLUtil.guessFileName(url, null, null));
picture_scrollview.setVisibility(View.VISIBLE);
InputStream bis = new ByteArrayInputStream(binaryData);
loading_picture.setVisibility(View.VISIBLE);
toot_picture.setEnabled(false);
new UploadActionAsyncTask(getApplicationContext(),bis,TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
f.write(binaryData);
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error) {
error.printStackTrace();
}
});
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error) {
}
});
}
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(receive_picture, new IntentFilter(Helper.RECEIVE_PICTURE));
toot_content.setText( String.format("\n%s", sharedContent));
}
attachments = new ArrayList<>();
@ -572,6 +575,14 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
}
}
@Override
public void onDestroy(){
super.onDestroy();
if( receive_picture != null)
LocalBroadcastManager.getInstance(this).unregisterReceiver(receive_picture);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String permissions[], @NonNull int[] grantResults) {
@ -595,15 +606,6 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
mToast.show();
}
//TODO: This overload takes care of the single URL coming from a text share.
public void uploadSharedImage(Uri image)
{
if (image != null) {
ArrayList<Uri> uri = new ArrayList<>();
uri.add(image);
uploadSharedImage(uri);
}
}
// Handles uploading shared images
public void uploadSharedImage(ArrayList<Uri> uri)
@ -1373,28 +1375,4 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
changeDrawableColor(TootActivity.this, R.drawable.ic_check, R.color.white);
}
}
// TODO: These two methods are part of the interface for ParserUtils
/*
These two methods are added to handle the grafted on code from Tusky,
they are part of its interface.
*/
@Override
public void onReceiveHeaderInfo(ParserUtils.HeaderInfo headerInfo) {
if (!TextUtils.isEmpty(headerInfo.title)) {
Toast.makeText(getApplicationContext(), headerInfo.title, Toast.LENGTH_SHORT).show();
if (!TextUtils.isEmpty(headerInfo.image)) {
Toast.makeText(getApplicationContext(), "We have an image", Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(), headerInfo.image, Toast.LENGTH_SHORT).show();
uploadSharedImage(Uri.parse(headerInfo.image));
}
}
}
@Override
public void onErrorHeaderInfo() {
Toast.makeText(this.getApplicationContext(), "An error has occurred.", Toast.LENGTH_SHORT).show();
}
}

View File

@ -0,0 +1,69 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastalab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Mastalab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Mastalab; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.asynctasks;
import android.os.AsyncTask;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.io.IOException;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveMetaDataInterface;
/**
* Created by Thomas on 02/09/2017.
* Retrieves metadata of a remote page
*/
public class RetrieveMetaDataAsyncTask extends AsyncTask<Void, Void, Void> {
private OnRetrieveMetaDataInterface listener;
private String url;
private boolean error = false;
private String imageUrl, content;
public RetrieveMetaDataAsyncTask(String url, OnRetrieveMetaDataInterface onRetrieveRemoteAccountInterface){
this.url = url;
this.listener = onRetrieveRemoteAccountInterface;
}
@Override
protected Void doInBackground(Void... params) {
String userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36";
try {
Document document = Jsoup.connect(url).userAgent(userAgent).get();
Elements metaOgTitle = document.select("meta[property=og:title]");
if (metaOgTitle != null) {
content = metaOgTitle.attr("content");
} else {
content = document.title();
}
Elements metaOgImage = document.select("meta[property=og:image]");
if (metaOgImage != null) {
imageUrl = metaOgImage.attr("content");
}
} catch (IOException | IndexOutOfBoundsException e) {
error = true;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveRemoteAccount(error, imageUrl, content);
}
}

View File

@ -253,7 +253,7 @@ public class Helper {
public static final String SEARCH_VALIDATE_ACCOUNT = "search_validate_account";
public static final String HEADER_ACCOUNT = "header_account";
public static final String RECEIVE_DATA = "receive_data";
public static final String RECEIVE_PICTURE = "receive_picture";
//User agent
public static final String USER_AGENT = "Mastalab/"+ BuildConfig.VERSION_NAME + " Android/"+ Build.VERSION.RELEASE;

View File

@ -0,0 +1,9 @@
package fr.gouv.etalab.mastodon.helper;
/**
* Created by Thomas on 02/09/2017.
*/
public class ManageHeader {
}

View File

@ -1,141 +0,0 @@
package fr.gouv.etalab.mastodon.helper;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import android.webkit.URLUtil;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.helper.HttpConnection;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
// TODO: Borrowed wholesale from Tusky
/**
* Inspect and get the information from a URL.
*/
public class ParserUtils {
public final static String carriageReturn = System.getProperty("line.separator");
final private static String QUOTE = "\"";
private static final String TAG = "ParserUtils";
private ParserListener parserListener;
public ParserUtils(ParserListener parserListener) {
this.parserListener = parserListener;
}
// TootActivity : EditText inside the onTextChanged
public String getPastedURLText(Context context) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
String pasteData;
if (clipboard.hasPrimaryClip()) {
// get what is in the clipboard
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
pasteData = item.getText().toString();
// If we share with an app, it's not only an url
List<String> strings = extractUrl(pasteData);
if (strings.size() > 0) {
String url = strings.get(0); // we assume that the first url is the good one
if (URLUtil.isValidUrl(url)) {
new ThreadHeaderInfo().execute(url);
}
}
}
return null;
}
public void putInClipboardManager(Context context, String string) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("", string);
clipboard.setPrimaryClip(clip);
}
/** parse the HTML page */
private HeaderInfo parsePageHeaderInfo(String urlStr) throws Exception {
Connection con = Jsoup.connect(urlStr);
HeaderInfo headerInfo = new HeaderInfo();
con.userAgent(HttpConnection.DEFAULT_UA);
Document doc = con.get();
// get info
String text;
Elements metaOgTitle = doc.select("meta[property=og:title]");
if (metaOgTitle != null) {
text = metaOgTitle.attr("content");
} else {
text = doc.title();
}
String imageUrl = null;
Elements metaOgImage = doc.select("meta[property=og:image]");
if (metaOgImage != null) {
imageUrl = metaOgImage.attr("content");
}
// set info
headerInfo.baseUrl = urlStr;
if (!TextUtils.isEmpty(text)) {
headerInfo.title = QUOTE + text + QUOTE;
}
if (!TextUtils.isEmpty(imageUrl)) {
headerInfo.image = (imageUrl);
}
return headerInfo;
}
public interface ParserListener {
void onReceiveHeaderInfo(HeaderInfo headerInfo);
void onErrorHeaderInfo();
}
public class HeaderInfo {
public String baseUrl;
public String title;
public String image;
}
private class ThreadHeaderInfo extends AsyncTask<String, Void, HeaderInfo> {
protected HeaderInfo doInBackground(String... urls) {
try {
String url = urls[0];
return parsePageHeaderInfo(url);
} catch (Exception e) {
Log.e(TAG, "ThreadHeaderInfo#parsePageHeaderInfo() failed." + e.getMessage());
return null;
}
}
protected void onPostExecute(HeaderInfo headerInfo) {
if (headerInfo != null) {
Log.i(TAG, "ThreadHeaderInfo#parsePageHeaderInfo() success." + headerInfo.title + " " + headerInfo.image);
parserListener.onReceiveHeaderInfo(headerInfo);
} else {
parserListener.onErrorHeaderInfo();
}
}
}
static List<String> extractUrl(String text) {
List<String> links = new ArrayList<>();
Matcher m = Patterns.WEB_URL.matcher(text);
while (m.find()) {
String url = m.group();
links.add(url);
}
return links;
}
}

View File

@ -0,0 +1,24 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastalab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Mastalab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Mastalab; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.interfaces;
/**
* Created by Thomas on 02/09/2017.
* Interface for retrieving meta data
*/
public interface OnRetrieveMetaDataInterface {
void onRetrieveRemoteAccount(boolean error, String pictureUrl, String content);
}

View File

@ -26,7 +26,6 @@ import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
@ -38,7 +37,6 @@ import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SwitchCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.support.design.widget.NavigationView;
@ -71,6 +69,7 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Stack;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveMetaDataAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoByIDAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
@ -81,6 +80,7 @@ import fr.gouv.etalab.mastodon.fragments.DisplayFollowRequestSentFragment;
import fr.gouv.etalab.mastodon.fragments.DisplayNotificationsFragment;
import fr.gouv.etalab.mastodon.fragments.DisplayScheduledTootsFragment;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveMetaDataInterface;
import fr.gouv.etalab.mastodon.interfaces.OnUpdateAccountInfoInterface;
import fr.gouv.etalab.mastodon.services.StreamingService;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
@ -107,7 +107,7 @@ import android.support.v4.app.FragmentStatePagerAdapter;
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface, ProviderInstaller.ProviderInstallListener {
implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface, ProviderInstaller.ProviderInstallListener, OnRetrieveMetaDataInterface {
private FloatingActionButton toot;
private HashMap<String, String> tagTile = new HashMap<>();
@ -621,17 +621,13 @@ public class MainActivity extends AppCompatActivity
if ("text/plain".equals(type)) {
String sharedSubject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
String sharedStream = null;
if( uri!= null)
sharedStream = uri.toString();
Log.v(Helper.TAG,"sharedStream1: " + sharedStream);
// ParserUtils is a class I borrowed from Tusky.
if (sharedText != null) {
new RetrieveMetaDataAsyncTask(sharedText, MainActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Intent intentToot = new Intent(getApplicationContext(), TootActivity.class);
Bundle b = new Bundle();
b.putString("sharedSubject", sharedSubject);
b.putString("sharedContent", sharedText);
b.putString("sharedStream", sharedStream);
intentToot.putExtras(b);
startActivity(intentToot);
}
@ -1080,6 +1076,14 @@ public class MainActivity extends AppCompatActivity
// appropriate action.
}
@Override
public void onRetrieveRemoteAccount(boolean error, String pictureUrl, String content) {
if( !error && pictureUrl != null) {
Intent intentSendImage = new Intent(Helper.RECEIVE_PICTURE);
intentSendImage.putExtra("pictureURL", pictureUrl);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intentSendImage);
}
}
/**