/* * Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source * Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package info.guardianproject.otr.app.im.app; import info.guardianproject.otr.OtrDataHandler; import info.guardianproject.otr.app.im.IChatSession; import info.guardianproject.otr.app.im.IChatSessionManager; import info.guardianproject.otr.app.im.IImConnection; import info.guardianproject.otr.app.im.R; import info.guardianproject.otr.app.im.engine.ImConnection; import info.guardianproject.otr.app.im.provider.Imps; import info.guardianproject.otr.app.im.service.ImServiceConstants; import info.guardianproject.util.LogCleaner; import info.guardianproject.util.SystemServices; import info.guardianproject.util.SystemServices.FileInfo; import java.io.File; import java.io.InputStream; import java.util.Collection; import java.util.Iterator; import java.util.Locale; import java.util.Set; import java.util.UUID; import net.java.otr4j.session.SessionStatus; import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentUris; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; public class ImUrlActivity extends Activity { private static final String TAG = "ImUrlActivity"; private static final int REQUEST_PICK_CONTACTS = RESULT_FIRST_USER + 1; private static final int REQUEST_CREATE_ACCOUNT = RESULT_FIRST_USER + 2; private static final int REQUEST_SIGNIN_ACCOUNT = RESULT_FIRST_USER + 3; private static final int REQUEST_START_MUC = RESULT_FIRST_USER + 4; private String mProviderName; private String mToAddress; private String mFromAddress; private String mHost; private IImConnection mConn; private IChatSessionManager mChatSessionManager; private Uri mSendUri; private String mSendType; private String mSendText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); doOnCreate(); } @Override protected void onResume() { super.onResume(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); } public void onDBLocked() { Intent intent = new Intent(getApplicationContext(), WelcomeActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); } void handleIntent() { ContentResolver cr = getContentResolver(); long providerId = -1; long accountId = -1; Collection<IImConnection> listConns = ((ImApp)getApplication()).getActiveConnections(); //look for active connections that match the host we need for (IImConnection conn : listConns) { try { long connProviderId = conn.getProviderId(); Cursor cursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(connProviderId)},null); Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( cursor, cr, connProviderId, false /* don't keep updated */, null /* no handler */); try { String domainToCheck = settings.getDomain(); if (domainToCheck != null && domainToCheck.length() > 0 && mHost.contains(domainToCheck)) { mConn = conn; providerId = connProviderId; accountId = conn.getAccountId(); break; } } finally { settings.close(); } } catch (RemoteException e) { e.printStackTrace(); } } //nothing active, let's see if non-active connections match if (mConn == null) { Cursor cursorProvider = initProviderCursor(); if (cursorProvider == null || cursorProvider.isClosed() || cursorProvider.getCount() == 0) { createNewAccount(); return; } else { while (cursorProvider.moveToNext()) { //make sure there is a stored password if (!cursorProvider.isNull(ACTIVE_ACCOUNT_PW_COLUMN)) { long cProviderId = cursorProvider.getLong(PROVIDER_ID_COLUMN); Cursor cursor = cr.query(Imps.ProviderSettings.CONTENT_URI,new String[] {Imps.ProviderSettings.NAME, Imps.ProviderSettings.VALUE},Imps.ProviderSettings.PROVIDER + "=?",new String[] { Long.toString(cProviderId)},null); Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( cursor, cr, cProviderId, false /* don't keep updated */, null /* no handler */); //does the conference host we need, match the settings domain for a logged in account String domainToCheck = settings.getDomain(); if (domainToCheck != null && domainToCheck.length() > 0 && mHost.contains(domainToCheck)) { providerId = cProviderId; accountId = cursorProvider.getLong(ACTIVE_ACCOUNT_ID_COLUMN); mConn = ((ImApp)getApplication()).getConnection(providerId); //now sign in signInAccount(accountId, providerId, cursorProvider.getString(ACTIVE_ACCOUNT_PW_COLUMN)); settings.close(); cursorProvider.close(); return; } settings.close(); } } cursorProvider.close(); } } if (mConn != null) { try { int state = mConn.getState(); accountId = mConn.getAccountId(); providerId = mConn.getProviderId(); if (state < ImConnection.LOGGED_IN) { Cursor cursorProvider = initProviderCursor(); while(cursorProvider.moveToNext()) { if (cursorProvider.getLong(ACTIVE_ACCOUNT_ID_COLUMN) == accountId) { signInAccount(accountId, providerId, cursorProvider.getString(ACTIVE_ACCOUNT_PW_COLUMN)); try { Thread.sleep (500); } catch (InterruptedException e1) { e1.printStackTrace(); }//wait here for three seconds mConn = ((ImApp)getApplication()).getConnection(providerId); break; } } cursorProvider.close(); } if (state == ImConnection.LOGGED_IN || state == ImConnection.SUSPENDED) { Uri data = getIntent().getData(); if (data.getScheme().equals("immu")) { this.openMultiUserChat(data); } else if (!isValidToAddress()) { showContactList(accountId); } else { openChat(providerId, accountId); } } } catch (RemoteException e) { // Ouch! Service died! We'll just disappear. Log.w("ImUrlActivity", "Connection disappeared!"); finish(); } } else { createNewAccount(); return; } } /* private void addAccount(long providerId) { Intent intent = new Intent(this, AccountActivity.class); intent.setAction(Intent.ACTION_INSERT); intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); // intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); if (mFromAddress != null) intent.putExtra("newuser", mFromAddress + '@' + mHost); startActivity(intent); }*/ private void editAccount(long accountId) { Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); Intent intent = new Intent(this, AccountActivity.class); intent.setAction(Intent.ACTION_EDIT); intent.setData(accountUri); intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); startActivityForResult(intent,REQUEST_SIGNIN_ACCOUNT); } private void signInAccount(long accountId, long providerId, String password) { //editAccount(accountId); // TODO sign in? security implications? SignInHelper signInHelper = new SignInHelper(this); signInHelper.setSignInListener(new SignInHelper.SignInListener() { public void connectedToService() { } public void stateChanged(int state, long accountId) { if (state == ImConnection.LOGGED_IN) { mHandlerRouter.post(new Runnable() { public void run () { handleIntent(); } }); } } }); signInHelper.signIn(password, providerId, accountId, true); } private void showContactList(long accountId) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Imps.Contacts.CONTENT_URI); intent.addCategory(ImApp.IMPS_CATEGORY); intent.putExtra("accountId", accountId); startActivity(intent); } private void openChat(long provider, long account) { try { IChatSessionManager manager = mConn.getChatSessionManager(); IChatSession session = manager.getChatSession(mToAddress); if (session == null) { session = manager.createChatSession(mToAddress,false); } Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, session.getId()); Intent intent = new Intent(Intent.ACTION_VIEW, data); intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, mToAddress); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, provider); intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, account); intent.addCategory(ImApp.IMPS_CATEGORY); startActivity(intent); } catch (RemoteException e) { // Ouch! Service died! We'll just disappear. Log.w("ImUrlActivity", "Connection disappeared!"); } } private boolean resolveInsertIntent(Intent intent) { Uri data = intent.getData(); if (data.getScheme().equals("ima")) { createNewAccount(); return true; } return false; } // private static final String USERNAME_PATTERN = "^[a-z0-9_-]{3,15}$"; //private static final String USERNAME_NON_LETTERS_UNICODE = "[^\\p{L}\\p{Nd}]+"; //private static final String USERNAME_NON_LETTERS_ALPHANUM = "[\\d[^\\w]]+"; private static final String USERNAME_ONLY_ALPHANUM = "[^A-Za-z0-9]"; private boolean resolveIntent(Intent intent) { Uri data = intent.getData(); mHost = data.getHost(); if (data.getScheme().equals("immu")) { mFromAddress = data.getUserInfo(); //remove username non-letters mFromAddress = mFromAddress.replaceAll(USERNAME_ONLY_ALPHANUM, "").toLowerCase(Locale.ENGLISH); String chatRoom = null; if (data.getPathSegments().size() > 0) { chatRoom = data.getPathSegments().get(0); //replace chat room name non-letters with underscores chatRoom = chatRoom.replaceAll("[\\d[^\\w]]+", "_"); mToAddress = chatRoom + '@' + mHost; mProviderName = findMatchingProvider(mHost); return true; } return false; } if (data.getScheme().equals("otr-in-band")) { this.openOtrInBand(data, intent.getType()); return true; } if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("resolveIntent: host=" + mHost); } if (TextUtils.isEmpty(mHost)) { Set<String> categories = intent.getCategories(); if (categories != null) { Iterator<String> iter = categories.iterator(); if (iter.hasNext()) { String category = iter.next(); String providerName = getProviderNameForCategory(category); mProviderName = findMatchingProvider(providerName); if (mProviderName == null) { Log.w(ImApp.LOG_TAG, "resolveIntent: IM provider " + category + " not supported"); return false; } } } mToAddress = data.getSchemeSpecificPart(); } else { mProviderName = findMatchingProvider(mHost); if (mProviderName == null) { Log.w(ImApp.LOG_TAG, "resolveIntent: IM provider " + mHost + " not supported"); return false; } String path = data.getPath(); if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) log("resolveIntent: path=" + path); if (!TextUtils.isEmpty(path)) { int index; if ((index = path.indexOf('/')) != -1) { mToAddress = path.substring(index + 1); } } } if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)) { log("resolveIntent: provider=" + mProviderName + ", to=" + mToAddress); } return true; } private String getProviderNameForCategory(String providerCategory) { return Imps.ProviderNames.XMPP; } private String findMatchingProvider(String provider) { if (TextUtils.isEmpty(provider)) { return null; } // if (provider.equalsIgnoreCase("xmpp")) // return Imps.ProviderNames.XMPP; return "Jabber (XMPP)"; //return Imps.ProviderNames.XMPP; } private boolean isValidToAddress() { if (TextUtils.isEmpty(mToAddress)) { return false; } if (mToAddress.indexOf('/') != -1) { return false; } return true; } private static void log(String msg) { Log.d(ImApp.LOG_TAG, "<ImUrlActivity> " + msg); } void openMultiUserChat(final Uri data) { new AlertDialog.Builder(this) .setTitle(getString(R.string.dialog_connect_chatroom_title)) .setMessage(getString(R.string.dialog_connect_chatroom_message)) .setPositiveButton(R.string.connect, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Intent intent = new Intent(ImUrlActivity.this, NewChatActivity.class); intent.setData(data); ImUrlActivity.this.startActivityForResult(intent, REQUEST_START_MUC); dialog.dismiss(); finish(); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { /* User clicked cancel so do some stuff */ finish(); } }) .create().show(); } void createNewAccount() { String username = getIntent().getData().getUserInfo(); String appCreateAcct = String.format(getString(R.string.allow_s_to_create_a_new_chat_account_for_s_),username); new AlertDialog.Builder(this) .setTitle(R.string.prompt_create_new_account_) .setMessage(appCreateAcct) .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mHandlerRouter.sendEmptyMessage(1); dialog.dismiss(); } }) .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { finish(); } }) .create().show(); } Handler mHandlerRouter = new Handler () { @Override public void handleMessage(Message msg) { if (msg.what == 1) { Uri uriAccountData = getIntent().getData(); if (uriAccountData.getScheme().equals("immu")) { //need to generate proper IMA url for account setup String randomJid = ((int)(Math.random()*1000))+""; String regUser = mFromAddress + randomJid; String regPass = UUID.randomUUID().toString().substring(0,16); String regDomain = mHost.replace("conference.", ""); uriAccountData = Uri.parse("ima://" + regUser + ':' + regPass + '@' + regDomain); } Intent intent = new Intent(ImUrlActivity.this, AccountActivity.class); intent.setAction(Intent.ACTION_INSERT); intent.setData(uriAccountData); startActivityForResult(intent,REQUEST_CREATE_ACCOUNT); } else if (msg.what == 2) { doOnCreate(); } } }; void openOtrInBand(final Uri data, final String type) { mSendType = getContentResolver().getType(data); if (mSendType != null ) { mSendUri = data; startContactPicker(); return; } else if (data.toString().startsWith(OtrDataHandler.URI_PREFIX_OTR_IN_BAND)) { String localUrl = data.toString().replaceFirst(OtrDataHandler.URI_PREFIX_OTR_IN_BAND, ""); FileInfo info = null; if (TextUtils.equals(data.getAuthority(), "com.android.contacts")) { info = SystemServices.getContactAsVCardFile(this, data); } else { info = SystemServices.getFileInfoFromURI(ImUrlActivity.this, data); } if (info != null && !TextUtils.isEmpty(info.path)) { mSendUri = Uri.fromFile(new File(info.path)); mSendType = type != null ? type : info.type; startContactPicker(); return; } } Toast.makeText(this, R.string.unsupported_incoming_data, Toast.LENGTH_LONG).show(); finish(); // make sure not to show this Activity's blank white screen } @Override protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) { if (resultCode == RESULT_OK) { if (requestCode == REQUEST_PICK_CONTACTS) { String username = resultIntent.getExtras().getString(ContactsPickerActivity.EXTRA_RESULT_USERNAME); long providerId = resultIntent.getExtras().getLong(ContactsPickerActivity.EXTRA_RESULT_PROVIDER); long accountId = resultIntent.getExtras().getLong(ContactsPickerActivity.EXTRA_RESULT_ACCOUNT); sendOtrInBand(username, providerId, accountId); finish(); } else if (requestCode == REQUEST_SIGNIN_ACCOUNT || requestCode == REQUEST_CREATE_ACCOUNT) { mHandlerRouter.postDelayed(new Runnable() { @Override public void run () { doOnCreate(); } }, 500); } } else { finish(); } } private void sendOtrInBand(String username, long providerId, long accountId) { try { IImConnection conn = ((ImApp)getApplication()).getConnection(providerId); mChatSessionManager = conn.getChatSessionManager(); IChatSession session = getChatSession(username); if (mSendText != null) session.sendMessage(mSendText); else if (mSendUri != null && session.getOtrChatSession() != null) { if (session.getOtrChatSession().getChatStatus() != SessionStatus.ENCRYPTED.ordinal()) { //can't do OTR transfer Toast.makeText(this, R.string.err_otr_share_no_encryption, Toast.LENGTH_LONG).show(); } else { try { String offerId = UUID.randomUUID().toString(); // Log.i(TAG, "mSendUrl " +mSendUrl); Uri vfsUri = null; if (ChatFileStore.isVfsUri(mSendUri)) vfsUri = mSendUri; else { InputStream is = getContentResolver().openInputStream(mSendUri); String fileName = mSendUri.getLastPathSegment(); FileInfo importInfo = SystemServices.getFileInfoFromURI(this, mSendUri); if (importInfo.type != null && importInfo.type.startsWith("image")) vfsUri = ChatFileStore.resizeAndImportImage(this, session.getId() + "", mSendUri, importInfo.type); else vfsUri = ChatFileStore.importContent(session.getId() + "", fileName, is); } FileInfo info = SystemServices.getFileInfoFromURI(this, vfsUri); session.offerData(offerId, info.path, mSendType ); Imps.insertMessageInDb( getContentResolver(), false, session.getId(), true, null, vfsUri.toString(), System.currentTimeMillis(), Imps.MessageType.OUTGOING_ENCRYPTED, // TODO show verified status 0, offerId, mSendType); } catch (Exception e) { Toast.makeText(this, R.string.unable_to_securely_share_this_file, Toast.LENGTH_LONG).show(); e.printStackTrace(); } } } } catch (RemoteException e) { e.printStackTrace(); } } private IChatSession getChatSession(String username) { if (mChatSessionManager != null) { try { IChatSession session = mChatSessionManager.getChatSession(username); if (session == null) session = mChatSessionManager.createChatSession(username,false); return session; } catch (RemoteException e) { LogCleaner.error(ImApp.LOG_TAG, "send message error",e); } } return null; } private void startContactPicker() { boolean noOnlineConnections = true; Uri.Builder builder = Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY.buildUpon(); Collection<IImConnection> listConns = ((ImApp)getApplication()).getActiveConnections(); for (IImConnection conn : listConns) { try { if (conn.getState() == ImConnection.LOGGED_IN) { try { mChatSessionManager = conn.getChatSessionManager(); long mProviderId = conn.getProviderId(); long mAccountId = conn.getAccountId(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); Uri data = builder.build(); Intent i = new Intent(Intent.ACTION_PICK, data); startActivityForResult(i, REQUEST_PICK_CONTACTS); noOnlineConnections = false; break; } catch (RemoteException e) { throw new RuntimeException(e); } } } catch (RemoteException re){} } if (noOnlineConnections) { Toast.makeText(this, R.string.no_connection_for_sending, Toast.LENGTH_LONG).show(); finish(); // quit this Activity, nothing online } } void showLockScreen() { Intent intent = new Intent(this, LockScreenActivity.class); // intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtra("originalIntent", getIntent()); startActivity(intent); finish(); } private void doOnCreate () { Intent intent = getIntent(); if (Intent.ACTION_INSERT.equals(intent.getAction())) { if (!resolveInsertIntent(intent)) { finish(); return; } } else if (Intent.ACTION_SEND.equals(intent.getAction())) { Uri streamUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); String mimeType = intent.getType(); String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (streamUri != null) openOtrInBand(streamUri, mimeType); else if (intent.getData() != null) openOtrInBand(intent.getData(), mimeType); else if (sharedText != null) { //do nothing for now :( mSendText = sharedText; startContactPicker(); } else finish(); } else if (Intent.ACTION_SENDTO.equals(intent.getAction())) { if (!resolveIntent(intent)) { finish(); return; } if (TextUtils.isEmpty(mToAddress)) { LogCleaner.warn(ImApp.LOG_TAG, "<ImUrlActivity>Invalid to address"); // finish(); return; } ImApp mApp = (ImApp)getApplication(); if (mApp.serviceConnected()) handleIntent(); else { mApp.callWhenServiceConnected(new Handler(), new Runnable() { public void run() { handleIntent(); } }); Toast.makeText(ImUrlActivity.this, R.string.starting_the_chatsecure_service_, Toast.LENGTH_LONG).show(); } } else { finish(); } } private Cursor initProviderCursor () { Uri uri = Imps.Provider.CONTENT_URI_WITH_ACCOUNT; // uri = uri.buildUpon().appendQueryParameter(ImApp.CACHEWORD_PASSWORD_KEY, pkey).build(); //just init the contentprovider db return getContentResolver().query(uri, PROVIDER_PROJECTION, Imps.Provider.CATEGORY + "=?" + " AND " + Imps.Provider.ACTIVE_ACCOUNT_USERNAME + " NOT NULL" /* selection */, new String[] { ImApp.IMPS_CATEGORY } /* selection args */, Imps.Provider.DEFAULT_SORT_ORDER); } @Override protected void onDestroy() { super.onDestroy(); } private static final String[] PROVIDER_PROJECTION = { Imps.Provider._ID, Imps.Provider.NAME, Imps.Provider.FULLNAME, Imps.Provider.CATEGORY, Imps.Provider.ACTIVE_ACCOUNT_ID, Imps.Provider.ACTIVE_ACCOUNT_USERNAME, Imps.Provider.ACTIVE_ACCOUNT_PW, Imps.Provider.ACTIVE_ACCOUNT_LOCKED, Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, Imps.Provider.ACCOUNT_PRESENCE_STATUS, Imps.Provider.ACCOUNT_CONNECTION_STATUS }; static final int PROVIDER_ID_COLUMN = 0; static final int PROVIDER_NAME_COLUMN = 1; static final int PROVIDER_FULLNAME_COLUMN = 2; static final int PROVIDER_CATEGORY_COLUMN = 3; static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; static final int ACTIVE_ACCOUNT_LOCKED = 7; static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; static final int ACCOUNT_PRESENCE_STATUS = 9; static final int ACCOUNT_CONNECTION_STATUS = 10; }