Ver Fonte

[add] SMS permission
[bugfix] local mode force sms app
[bugfix] legacy inject js
[add] read all sms
[WIP] static data blob

isundil há 8 anos atrás
pai
commit
d0a6f61e79

+ 2 - 0
app/src/main/AndroidManifest.xml

@@ -3,6 +3,8 @@
     package="com.knacki.mimou">
 
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS" />
 
     <application
         android:allowBackup="true"

+ 0 - 39
app/src/main/java/com/knacki/mimou/JsInterface.java

@@ -1,39 +0,0 @@
-package com.knacki.mimou;
-
-import android.webkit.JavascriptInterface;
-
-import com.knacki.mimou.activity.MainActivity;
-import com.knacki.mimou.preference.CredentialHolder;
-import com.knacki.mimou.preference.UserSettings;
-
-import java.util.concurrent.Callable;
-
-/**
- * Created by thibal on 1/4/18.
- */
-public class JsInterface {
-    MainActivity mainActivity;
-
-    public void setActivity(MainActivity mainActivity) {
-        this.mainActivity = mainActivity;
-    }
-
-    @JavascriptInterface
-    public void logout() {
-        CredentialHolder.removeToken(mainActivity);
-        mainActivity.showLogin();
-    }
-
-    public void readSmsPermission() {
-        mainActivity.requestSmsPermission(new TypedCallback<Boolean>() {
-            @Override
-            public void onResult(Boolean result) {
-                mainActivity.injectJavascript("onSmsPermissionResult(" +result +")");
-            }
-        });
-    }
-
-    public abstract class TypedCallback <T>{
-        public abstract void onResult(T result);
-    }
-}

+ 2 - 1
app/src/main/java/com/knacki/mimou/activity/LoginActivity.java

@@ -27,7 +27,8 @@ public class LoginActivity extends AppCompatActivity {
             @Override
             public void onClick(View view) {
                 CredentialHolder.setLocalUse(LoginActivity.this);
-                startActivity(new Intent(LoginActivity.this, MainActivity.class));
+                MainActivity.needReload = true;
+                finish();
             }
         });
     }

+ 23 - 30
app/src/main/java/com/knacki/mimou/activity/MainActivity.java

@@ -7,36 +7,30 @@ import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.support.annotation.NonNull;
 import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
-import android.util.Base64;
-import android.webkit.ValueCallback;
 import android.webkit.WebChromeClient;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.widget.Toast;
 
 import com.knacki.mimou.BuildConfig;
+import com.knacki.mimou.bridge.JavascriptFunction;
 import com.knacki.mimou.preference.CredentialHolder;
-import com.knacki.mimou.JsInterface;
+import com.knacki.mimou.bridge.JsInterface;
 import com.knacki.mimou.R;
-import com.knacki.mimou.preference.UserSettings;
 
-import java.io.UnsupportedEncodingException;
-import java.security.Permissions;
-import java.util.concurrent.Callable;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
 public class MainActivity extends AppCompatActivity {
     public final static int SMS_PERMISSION_CALLBACK = 42;
     protected WebView web;
     protected JsInterface interfaceMimouDroid;
+    public static boolean needReload = true;
     private static Logger log = Logger.getLogger(MainActivity.class.getName());
+    private JsInterface.TypedCallback<Boolean> currentCallback = null;
+
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -70,7 +64,14 @@ public class MainActivity extends AppCompatActivity {
 
         web.getSettings().setJavaScriptEnabled(true);
         web.addJavascriptInterface(interfaceMimouDroid, "__native");
-        web.loadUrl(CredentialHolder.isLocal(this) ? (getString(R.string.mimouUrl) + (getString(R.string.localUrl))) : getString(R.string.mimouUrl));
+        needReload = true;
+        reload();
+    }
+
+    public void reload() {
+        if (needReload)
+            web.loadUrl(CredentialHolder.isLocal(this) ? (getString(R.string.mimouUrl) + (getString(R.string.localUrl))) : getString(R.string.mimouUrl));
+        needReload = false;
     }
 
     public void showLogin() {
@@ -80,29 +81,20 @@ public class MainActivity extends AppCompatActivity {
     @Override
     protected void onResume() {
         super.onResume();
-        if (!CredentialHolder.hasCredential(this)) {
+        if (!CredentialHolder.hasCredential(this))
             showLogin();
-        }
+        else
+            reload();
         interfaceMimouDroid.setActivity(this);
     }
 
-    public void injectJavascript(String js) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT  || false) {
-            web.evaluateJavascript(js, new ValueCallback<String>() {
-                @Override
-                public void onReceiveValue(String s) {
-                }
-            });
-        } else {
-            try {
-                web.loadUrl("data:text/html;charset=utf-8;base64," + Base64.encodeToString(("<script>" +js +"</script>").getBytes("UTF-8"), Base64.DEFAULT));
-            } catch (UnsupportedEncodingException e) {
-                log.log(Level.SEVERE, e.getMessage(), e);
-            }
-        }
+    private void runJavascript(String js) {
+        web.loadUrl("javascript:" +js);
     }
 
-    private JsInterface.TypedCallback<Boolean> currentCallback = null;
+    public void runJavascript(JavascriptFunction fnc) {
+        runJavascript(fnc.toString());
+    }
 
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
@@ -139,11 +131,12 @@ public class MainActivity extends AppCompatActivity {
                         callback.onResult(false);
                     }
                 })
+                .setCancelable(false)
                 .show();
     }
 
     public void requestSmsPermission(JsInterface.TypedCallback<Boolean> callback) {
-        Boolean userPermission =  UserSettings.readSms(this);
+        Boolean userPermission =  CredentialHolder.hasSMSPermission(this);
         if (userPermission == null) {
             promptSmsPermission(callback);
         } else if (userPermission == false) {

+ 11 - 0
app/src/main/java/com/knacki/mimou/bridge/JSONObjifiable.java

@@ -0,0 +1,11 @@
+package com.knacki.mimou.bridge;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public interface JSONObjifiable {
+    JSONObject toJSON()throws JSONException;
+}

+ 53 - 0
app/src/main/java/com/knacki/mimou/bridge/JavascriptFunction.java

@@ -0,0 +1,53 @@
+package com.knacki.mimou.bridge;
+
+import com.knacki.mimou.sms.Sms;
+import com.knacki.mimou.sms.SmsReader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+
+public class JavascriptFunction {
+    private StringBuilder fncStr;
+    private boolean firstArg = true;
+    private boolean immutable = false;
+
+    JavascriptFunction(String fncName) {
+        fncStr = new StringBuilder(fncName).append('(');
+    }
+
+    private static String escapeStr(String s) {
+        return s.replaceAll("\"", "\\\"");
+    }
+
+    public JavascriptFunction addArgument(String arg) {
+        if (!immutable) {
+            if (!firstArg)
+                fncStr.append(',');
+            fncStr.append(escapeStr(arg));
+            firstArg = false;
+        }
+        return this;
+    }
+
+    public JavascriptFunction addArgument(boolean arg) {
+        if (!immutable) {
+            if (!firstArg)
+                fncStr.append(',');
+            fncStr.append(arg ? "true" : "false");
+            firstArg = false;
+        }
+        return this;
+    }
+
+    public String toString() {
+        if (!immutable) {
+            fncStr.append(')');
+            immutable = true;
+        }
+        return fncStr.toString();
+    }
+}

+ 128 - 0
app/src/main/java/com/knacki/mimou/bridge/JsInterface.java

@@ -0,0 +1,128 @@
+package com.knacki.mimou.bridge;
+
+import android.webkit.JavascriptInterface;
+
+import com.knacki.mimou.activity.MainActivity;
+import com.knacki.mimou.contact.Contact;
+import com.knacki.mimou.contact.ContactReader;
+import com.knacki.mimou.preference.CredentialHolder;
+import com.knacki.mimou.sms.Conversation;
+import com.knacki.mimou.sms.ConversationList;
+import com.knacki.mimou.sms.Sms;
+import com.knacki.mimou.sms.SmsReader;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Created by thibal on 1/4/18.
+ */
+public class JsInterface {
+    private final Logger log = Logger.getLogger(JsInterface.class.getName());
+    MainActivity mainActivity;
+
+    public void setActivity(MainActivity mainActivity) {
+        this.mainActivity = mainActivity;
+    }
+
+    @JavascriptInterface
+    public void logout() {
+        CredentialHolder.removeToken(mainActivity);
+        mainActivity.showLogin();
+    }
+
+    @JavascriptInterface
+    public void readSmsPermission(final String fncName) {
+        mainActivity.requestSmsPermission(new TypedCallback<Boolean>() {
+            @Override
+            public void onResult(Boolean result) {
+                mainActivity.runJavascript(new JavascriptFunction(fncName).addArgument(result));
+            }
+        });
+    }
+
+    @JavascriptInterface
+    public String getSms() {
+        List<Sms> smsList = SmsReader.readAll(mainActivity);
+        JSONArray smsArray = new JSONArray();
+
+        for (Sms sms: smsList) {
+            try {
+                smsArray.put(sms.toJSON());
+            } catch (JSONException e) {
+                log.log(Level.SEVERE, "Cannot stringify message", e);
+            }
+        }
+        return smsArray.toString();
+    }
+
+    @JavascriptInterface
+    public String getStatic() {
+        JSONObject staticContent = new JSONObject();
+
+        getStaticChannels(staticContent);
+        getStaticUsers(staticContent);
+
+        JSONObject wrapper = new JSONObject();
+        try {
+            wrapper.put("static", staticContent);
+        } catch (JSONException e) {
+            log.log(Level.SEVERE, "Cannot JSONify static content", e);
+        }
+        return wrapper.toString();
+    }
+
+    private void getStaticUsers(JSONObject staticContent) {
+        JSONArray jsonUsers = new JSONArray();
+        JSONObject genericContent = new JSONObject();
+
+        for (Contact i: ContactReader.getAllContacts(mainActivity)) {
+            try {
+                jsonUsers.put(i.toJSON());
+            } catch (JSONException e) {
+                log.log(Level.SEVERE, "Cannot stringify contact " +i.getName(), e);
+            }
+        }
+
+        try {
+            genericContent.put("users", jsonUsers);
+            staticContent.put("SIM|generic", genericContent);
+        } catch (JSONException e) {
+            log.severe("Cannot stringify static users");
+        }
+    }
+
+    private void getStaticChannels(JSONObject staticContent) {
+        for (Map.Entry<Integer, ConversationList> conversationBySimcard: SmsReader.getAllConversations(mainActivity).entrySet()) {
+            JSONObject simConversations = new JSONObject();
+            JSONArray simChannels = new JSONArray();
+
+            for (Conversation i: conversationBySimcard.getValue()) {
+                try {
+                    simChannels.put(i.toJSON());
+                } catch (JSONException e) {
+                    log.log(Level.SEVERE, "Cannot stringify conversation " +i.getAddress(), e);
+                }
+            }
+            try {
+                simConversations.put("channels", simChannels);
+                staticContent.put("SIM|" +conversationBySimcard.getKey(), simConversations);
+            } catch (JSONException e) {
+                log.log(Level.SEVERE, "Cannot stringify conversations for sim id " +conversationBySimcard.getKey(), e);
+            }
+        }
+    }
+
+    public abstract class TypedCallback <T>{
+        public abstract void onResult(T result);
+    }
+}

+ 36 - 0
app/src/main/java/com/knacki/mimou/contact/Contact.java

@@ -0,0 +1,36 @@
+package com.knacki.mimou.contact;
+
+import com.knacki.mimou.bridge.JSONObjifiable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class Contact implements JSONObjifiable {
+    private final String name;
+
+    public Contact(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public JSONObject toJSON() throws JSONException {
+        JSONObject o = new JSONObject();
+        /*
+            "id":"..",
+            "real_name":"..",
+            "email":"..",
+            "first_name":"..",
+            "addr":"[..]"
+         */
+        o.put("name", name);
+        o.put("deleted", false);
+        return o;
+    }
+}

+ 15 - 0
app/src/main/java/com/knacki/mimou/contact/ContactReader.java

@@ -0,0 +1,15 @@
+package com.knacki.mimou.contact;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class ContactReader {
+    public static ArrayList<Contact> getAllContacts(Context c) {
+        // FIXME
+        return new ArrayList<>();
+    }
+}

+ 11 - 0
app/src/main/java/com/knacki/mimou/preference/CredentialHolder.java

@@ -9,6 +9,7 @@ import android.content.SharedPreferences;
 public abstract class CredentialHolder extends PreferenceHolder {
     public static final String TOKEN_LOCAL = "_local";
     private static final String PREF_SECURITY_TOKEN = "securityToken";
+    private final static String USE_SMS = "smsApp";
 
     public static void setLocalUse(Context c) {
         getSharedPreferences(c).edit().putString(PREF_SECURITY_TOKEN, TOKEN_LOCAL).commit();
@@ -38,4 +39,14 @@ public abstract class CredentialHolder extends PreferenceHolder {
     public static void removeToken(Context c) {
         getSharedPreferences(c).edit().remove(PREF_SECURITY_TOKEN).commit();
     }
+
+    public static Boolean hasSMSPermission(Context c) {
+        SharedPreferences p = getSharedPreferences(c);
+        if (p.contains(PREF_SECURITY_TOKEN) && p.getString(PREF_SECURITY_TOKEN, "").equals(TOKEN_LOCAL))
+            return true;
+        if (p.contains(USE_SMS)) {
+            return getSharedPreferences(c).getBoolean(USE_SMS, false);
+        }
+        return null;
+    }
 }

+ 0 - 9
app/src/main/java/com/knacki/mimou/preference/UserSettings.java

@@ -8,13 +8,4 @@ import android.content.SharedPreferences;
  */
 
 public class UserSettings extends PreferenceHolder {
-    private final static String USE_SMS = "smsApp";
-
-    public static Boolean readSms(Context c) {
-        SharedPreferences p = getSharedPreferences(c);
-        if (p.contains(USE_SMS)) {
-            return getSharedPreferences(c).getBoolean(USE_SMS, false);
-        }
-        return null;
-    }
 }

+ 80 - 0
app/src/main/java/com/knacki/mimou/sms/Conversation.java

@@ -0,0 +1,80 @@
+package com.knacki.mimou.sms;
+
+import android.content.Context;
+import android.telephony.PhoneNumberUtils;
+
+import com.knacki.mimou.bridge.JSONObjifiable;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class Conversation implements JSONObjifiable{
+    private final String address;
+    private int messageCount = 0;
+    private int unreadCount = 0;
+    private long created = Long.MAX_VALUE;
+    private ArrayList<String> members = new ArrayList<>();
+    private Sms lastSms = null;
+    private Sms firstUnread = null;
+
+    public Conversation(String address) {
+        this.address = address;
+    }
+
+    public Conversation addMessage(Context c, Sms sms) {
+        messageCount++;
+        if (lastSms == null || lastSms.getDate() < sms.getDate()) {
+            lastSms = sms;
+        }
+        if (!sms.isRead()) {
+            if (firstUnread == null || firstUnread.getDate() > sms.getDate())
+                firstUnread = sms;
+            unreadCount++;
+        }
+
+        boolean containsMember = false;
+        for (String i: members) {
+            if (PhoneNumberUtils.compare(c, i, sms.getAddress())) {
+                containsMember = true;
+                break;
+            }
+        }
+        if (!containsMember) {
+            members.add(sms.getAddress());
+        }
+
+        created = Math.min(created, sms.getDate());
+        return this;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    @Override
+    public JSONObject toJSON() throws JSONException {
+        JSONObject obj = new JSONObject();
+        obj.put("created", created);
+        obj.put("id", address);
+        obj.put("is_archived", false);
+        obj.put("is_member", true);
+        JSONArray membersArr = new JSONArray();
+        for (String i: members)
+            membersArr.put(i);
+        obj.put("members", membersArr);
+        obj.put("is_private", true);
+        obj.put("messageCount", messageCount);
+        obj.put("name", "address"); // FIXME
+        if (lastSms != null)
+            obj.put("last_msg", lastSms.getDate());
+        if (unreadCount > 0)
+            obj.put("last_read", 1); // FIXME
+        return obj;
+    }
+}

+ 29 - 0
app/src/main/java/com/knacki/mimou/sms/ConversationList.java

@@ -0,0 +1,29 @@
+package com.knacki.mimou.sms;
+
+import android.content.Context;
+import android.telephony.PhoneNumberUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class ConversationList extends ArrayList<Conversation> {
+    private final Context ctx;
+
+    public ConversationList(Context ctx) {
+        this.ctx = ctx;
+    }
+
+    public ConversationList() {
+        ctx = null;
+    }
+
+    Conversation findByAddress(String addr) {
+        for (Conversation i: this) {
+            if ((ctx == null && PhoneNumberUtils.compare(i.getAddress(), addr)) || (ctx != null && PhoneNumberUtils.compare(ctx, i.getAddress(), addr)))
+                return i;
+        }
+        return null;
+    }
+}

+ 49 - 0
app/src/main/java/com/knacki/mimou/sms/Sms.java

@@ -0,0 +1,49 @@
+package com.knacki.mimou.sms;
+
+import com.knacki.mimou.bridge.JSONObjifiable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class Sms implements JSONObjifiable {
+    private final String address;
+    private final String msg;
+    private final long ts;
+    private final boolean read;
+    private final boolean in;
+    private final Integer simId;
+
+    public Sms(String address, String textContent, long ts, boolean isRead, boolean isIn, Integer simId) {
+        this.address = address;
+        this.msg = textContent;
+        this.ts = ts;
+        this.read = isRead;
+        this.simId = simId;
+        in = isIn;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public long getDate() {
+        return ts;
+    }
+
+    public Integer getSimId() {
+        return simId;
+    }
+
+    public boolean isRead() { return read; }
+
+    @Override
+    public JSONObject toJSON() throws JSONException {
+        JSONObject res = new JSONObject();
+        res.put("from", address);
+        res.put("msg", msg);
+        return res;
+    }
+}

+ 75 - 0
app/src/main/java/com/knacki/mimou/sms/SmsReader.java

@@ -0,0 +1,75 @@
+package com.knacki.mimou.sms;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class SmsReader {
+    private static final int SMS_READ = 1;
+    private static final int SMS_INBOX = 1;
+    private static final Uri ALL_SMS_URI = Uri.parse("content://sms");
+
+    private static final String FIELD_ADDRESS = "address";
+    private static final String FIELD_BODY = "body";
+    private static final String FIELD_DATE = "date";
+    private static final String FIELD_READ = "read";
+    private static final String FIELD_TYPE = "type";
+    private static final String FIELD_SIM_ID = "sim_id";
+
+    private static Cursor queryAllSms(Context c) {
+        return c.getContentResolver().query(ALL_SMS_URI, new String[]{ FIELD_ADDRESS, FIELD_BODY, FIELD_DATE, FIELD_READ, FIELD_TYPE, FIELD_SIM_ID }, null, null, null);
+    }
+
+    public static ArrayList<Sms> readAll(Context c) {
+        ArrayList<Sms> smsArray = new ArrayList<>();
+        Cursor cursor = queryAllSms(c);
+
+        if (cursor.moveToFirst()) {
+            do {
+                smsArray.add(parseSms(cursor));
+            } while (cursor.moveToNext());
+        }
+        return smsArray;
+    }
+
+    public static HashMap<Integer, ConversationList> getAllConversations(Context c) {
+        HashMap<Integer, ConversationList> conversationList = new HashMap<>();
+        Cursor cursor = queryAllSms(c);
+
+        if (cursor.moveToFirst()) {
+            do {
+                Sms i = parseSms(cursor);
+                ConversationList list = conversationList.get(i.getSimId());
+                if (list == null) {
+                    list = new ConversationList(c);
+                    conversationList.put(i.getSimId(), list);
+                }
+
+                Conversation conversation = list.findByAddress(i.getAddress());
+                if (conversation == null) {
+                    conversation = new Conversation(i.getAddress());
+                    list.add(conversation);
+                }
+
+                conversation.addMessage(c, i);
+            } while (cursor.moveToNext());
+        }
+        return conversationList;
+    }
+
+    private static Sms parseSms(Cursor c) {
+        return new Sms(
+                c.getString(c.getColumnIndex(FIELD_ADDRESS)),
+                c.getString(c.getColumnIndex(FIELD_BODY)),
+                c.getLong(c.getColumnIndex(FIELD_DATE)),
+                c.getInt(c.getColumnIndex(FIELD_READ)) == SMS_READ,
+                c.getInt(c.getColumnIndex(FIELD_TYPE)) == SMS_INBOX,
+                c.getInt(c.getColumnIndex(FIELD_SIM_ID)));
+    }
+}

+ 7 - 0
app/src/main/java/com/knacki/mimou/sms/SmsReceiver.java

@@ -0,0 +1,7 @@
+package com.knacki.mimou.sms;
+
+/**
+ * Created by thibal on 1/5/18.
+ */
+public class SmsReceiver {
+}