/*
Copyright (C) 2013-2015, Silent Circle, LLC. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Any redistribution, use, or modification is done solely for personal
benefit and not for any commercial purpose or for monetary gain
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name Silent Circle nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.silentcircle.silenttext.activity;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.webkit.MimeTypeMap;
import com.silentcircle.silenttext.Action;
import com.silentcircle.silenttext.Extra;
import com.silentcircle.silenttext.Manifest;
import com.silentcircle.silenttext.R;
import com.silentcircle.silenttext.application.SilentTextApplication;
import com.silentcircle.silenttext.dialog.ShareDialogFragment;
import com.silentcircle.silenttext.fragment.ContactListFragment;
import com.silentcircle.silenttext.location.LocationObserver;
import com.silentcircle.silenttext.location.LocationUtils;
import com.silentcircle.silenttext.location.OnLocationReceivedListener;
import com.silentcircle.silenttext.model.Contact;
import com.silentcircle.silenttext.model.Conversation;
import com.silentcircle.silenttext.model.MessageState;
import com.silentcircle.silenttext.model.event.Event;
import com.silentcircle.silenttext.model.event.Message;
import com.silentcircle.silenttext.model.event.OutgoingMessage;
import com.silentcircle.silenttext.model.json.util.JSONSirenDecorator;
import com.silentcircle.silenttext.repository.ConversationRepository;
import com.silentcircle.silenttext.repository.EventRepository;
import com.silentcircle.silenttext.util.AttachmentUtils;
public class ShareActivity extends SilentActivity implements ContactListFragment.Callback {
private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance();
private static String getPrintableHistory( List<Event> events ) {
StringBuilder history = new StringBuilder();
for( int i = 0; i < events.size(); i++ ) {
Event event = events.get( i );
if( event instanceof Message && ( ( (Message) event ).expires() || ( (Message) event ).getSiren() == null ) ) {
continue;
}
history.append( "[" ).append( DATE_FORMAT.format( new Date( event.getTime() ) ) ).append( "] " );
if( event instanceof Message ) {
Message message = (Message) event;
history.append( withoutDomain( message.getSender() ) ).append( ": " );
try {
JSONObject json = new JSONObject( message.getText() );
if( json.has( "message" ) ) {
history.append( json.getString( "message" ) );
} else {
history.append( "(attachment)" );
}
} catch( JSONException exception ) {
history.append( message.getText() );
}
} else {
history.append( event.getText() );
}
history.append( "\n" );
}
return history.toString();
}
private static String withoutDomain( String fullAddress ) {
return fullAddress == null ? null : fullAddress.replaceAll( "^(.+)@(.+)$", "$1" );
}
private String savedId;
private OutgoingMessage createOutgoingMessage( Conversation conversation, JSONObject siren, Location location ) {
JSONSirenDecorator.decorateSirenForConversation( siren, conversation, location, true );
OutgoingMessage message = new OutgoingMessage( getUsername(), siren.toString() );
message.setId( UUID.randomUUID().toString() );
return message;
}
private OutgoingMessage createOutgoingMessage( Conversation conversation, Location location ) {
return createOutgoingMessage( conversation, new JSONObject(), location );
}
private OutgoingMessage createOutgoingMessage( Conversation conversation, String text, Uri uri, String mimeType, Location location ) {
JSONObject siren = new JSONObject();
JSONSirenDecorator.decorateSirenForAttachment( this, siren, uri, mimeType );
JSONSirenDecorator.decorateSirenForConversation( siren, conversation, location, true );
if( text != null ) {
try {
siren.put( "message", text );
} catch( JSONException impossible ) {
// Ignore.
}
}
OutgoingMessage message = new OutgoingMessage( getUsername(), siren.toString() );
message.setConversationID( conversation.getPartner().getUsername() );
message.setId( UUID.randomUUID().toString() );
if( conversation.hasBurnNotice() ) {
message.setBurnNotice( conversation.getBurnDelay() );
}
return message;
}
private String getMIMEType( Intent intent ) {
String mimeType = null;
mimeType = intent.getType();
if( mimeType != null ) {
return mimeType;
}
mimeType = getContentResolver().getType( intent.getData() );
if( mimeType != null ) {
return mimeType;
}
String fileName = AttachmentUtils.getFileNameFromURI( this, intent.getData() );
if( fileName == null ) {
return null;
}
String extension = AttachmentUtils.getExtensionFromFileName( fileName );
if( extension == null ) {
return null;
}
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( extension );
return mimeType;
}
@Override
public void onBeginLoading() {
beginLoading( R.id.content );
}
@Override
public void onContactSelected( Contact contact ) {
sendFile( contact.getUsername() );
}
@Override
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.fragment );
setTitle( R.string.send_to );
findViewById( R.id.error ).setVisibility( View.GONE );
Bundle b = getIntent().getExtras();
if( b != null ) {
savedId = b.getString( ShareDialogFragment.SAVED_ID );
}
FragmentManager manager = getFragmentManager();
Fragment fragment = manager.findFragmentById( R.id.content );
if( fragment == null ) {
manager.beginTransaction().add( R.id.content, new ContactListFragment() ).commit();
}
if( getIntent() != null ) {
setIntent( getIntent() );
}
}
@Override
public void onFinishLoading() {
finishLoading( R.id.content );
}
@Override
protected void onNewIntent( Intent intent ) {
super.onNewIntent( intent );
setIntent( intent );
}
@Override
protected void onResume() {
super.onResume();
if( !isUnlocked() ) {
requestUnlock();
return;
}
if( !isActivated() ) {
requestActivation();
return;
}
}
protected void save( Conversation conversation, OutgoingMessage message ) {
if( message != null ) {
ConversationRepository conversations = getConversations();
if( conversations != null && conversation != null ) {
EventRepository events = conversations.historyOf( conversation );
if( events != null ) {
events.save( message );
}
}
}
}
protected void sendChatMessageAsAttachment( final Conversation conversation, final CharSequence text ) {
if( conversation != null && LocationUtils.isLocationSharingAvailable( this ) && conversation.isLocationEnabled() ) {
LocationObserver.observe( this, new OnLocationReceivedListener() {
@Override
public void onLocationReceived( Location location ) {
sendChatMessageAsAttachment( conversation, text, location );
}
@Override
public void onLocationUnavailable() {
onLocationReceived( null );
}
} );
return;
}
sendChatMessageAsAttachment( conversation, text, null );
}
public void sendChatMessageAsAttachment( Conversation conversation, CharSequence text, Location location ) {
OutgoingMessage message = createOutgoingMessage( conversation, location );
message.setState( MessageState.UNKNOWN );
save( conversation, message );
Intent encrypt = Action.ENCRYPT.intent();
encrypt.setDataAndType( null, "text/plain" );
Extra.PARTNER.to( encrypt, conversation.getPartner().getUsername() );
Extra.ID.to( encrypt, message.getId() );
Extra.TEXT.to( encrypt, text );
sendBroadcast( encrypt, Manifest.permission.WRITE );
}
protected void sendFile( Conversation conversation, Intent intent ) {
sendFile( conversation, intent, null );
}
protected void sendFile( Conversation conversation, Intent intent, Location location ) {
final String remoteUserID = conversation.getPartner().getUsername();
Uri uri = (Uri) intent.getParcelableExtra( Intent.EXTRA_STREAM );
String text = intent.getStringExtra( Intent.EXTRA_TEXT );
if( uri == null && text == null ) {
if( !TextUtils.isEmpty( savedId ) ) {
ConversationRepository conversations = SilentTextApplication.from( this ).getConversations();
Conversation c = conversations.findByPartner( savedId );
EventRepository history = conversations.historyOf( c );
List<Event> messages = history.list();
sendChatMessageAsAttachment( conversation, getPrintableHistory( messages ), null );
finish();
}
return;
}
long size = AttachmentUtils.getFileSize( this, uri );
if( uri != null ) {
if( size >= AttachmentUtils.FILE_SIZE_LIMIT ) {
AttachmentUtils.showFileSizeErrorDialog( this, new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
setResult( RESULT_CANCELED );
finish();
}
} );
return;
}
}
OutgoingMessage message = createOutgoingMessage( conversation, text, uri, intent.getType(), location );
if( uri == null ) {
message.setState( MessageState.COMPOSED );
} else {
message.setState( MessageState.UNKNOWN );
}
boolean isTalkingToSelf = isSelf( remoteUserID );
if( isTalkingToSelf ) {
message.setState( MessageState.SENT );
}
getConversations().historyOf( conversation ).save( message );
if( uri == null ) {
if( !isTalkingToSelf ) {
transition( remoteUserID, message.getId() );
}
viewConversation( remoteUserID );
finish();
return;
}
Intent encrypt = Action.ENCRYPT.intent();
encrypt.setDataAndType( uri, getMIMEType( intent ) );
Extra.PARTNER.to( encrypt, remoteUserID );
Extra.ID.to( encrypt, message.getId() );
sendBroadcast( encrypt, Manifest.permission.WRITE );
if( size > AttachmentUtils.FILE_SIZE_WARNING_THRESHOLD ) {
AttachmentUtils.showFileSizeWarningDialog( this, remoteUserID, message.getId(), new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
viewConversation( remoteUserID );
finish();
}
} );
return;
}
viewConversation( remoteUserID );
finish();
}
protected void sendFile( String remoteUserID ) {
boolean existed = getConversations().exists( remoteUserID );
final Conversation conversation = getOrCreateConversation( remoteUserID );
if( !existed ) {
getNative().connect( remoteUserID );
}
if( LocationUtils.isLocationSharingAvailable( this ) && conversation.isLocationEnabled() ) {
LocationObserver.observe( this, new OnLocationReceivedListener() {
@Override
public void onLocationReceived( Location location ) {
sendFile( conversation, getIntent(), location );
}
@Override
public void onLocationUnavailable() {
sendFile( conversation, getIntent() );
}
} );
} else {
sendFile( conversation, getIntent() );
}
}
protected void viewConversation( String remoteUserID ) {
Intent intent = new Intent( this, ConversationActivity.class );
Extra.PARTNER.to( intent, remoteUserID );
intent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_TOP );
startActivity( intent );
}
}