org.matrix.androidsdk.fragments.MatrixMessagesFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.matrix.androidsdk.fragments.MatrixMessagesFragment.java

Source

/*
 * Copyright 2015 OpenMarket Ltd
 * Copyright 2017 Vector Creations Ltd
 *
 * 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 org.matrix.androidsdk.fragments;

import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;

import org.matrix.androidsdk.util.Log;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.data.EventTimeline;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.data.RoomPreviewData;
import org.matrix.androidsdk.data.RoomState;
import org.matrix.androidsdk.listeners.IMXEventListener;
import org.matrix.androidsdk.listeners.MXEventListener;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.RoomResponse;
import org.matrix.androidsdk.rest.model.Sync.RoomSync;
import org.matrix.androidsdk.rest.model.Sync.RoomSyncState;
import org.matrix.androidsdk.rest.model.Sync.RoomSyncTimeline;

import java.util.List;

/**
 * A non-UI fragment containing logic for extracting messages from a room, including handling
 * pagination. For a UI implementation of this, see {@link MatrixMessageListFragment}.
 */
public class MatrixMessagesFragment extends Fragment {
    private static final String LOG_TAG = "MatrixMessagesFragment";

    /**
     * The room ID to get messages for.
     * Fragment argument: String.
     */
    public static final String ARG_ROOM_ID = "org.matrix.androidsdk.fragments.MatrixMessageFragment.ARG_ROOM_ID";

    public static MatrixMessagesFragment newInstance(MXSession session, String roomId,
            MatrixMessagesListener listener) {
        MatrixMessagesFragment fragment = new MatrixMessagesFragment();
        Bundle args = new Bundle();

        if (null == listener) {
            throw new RuntimeException("Must define a listener.");
        }

        if (null == session) {
            throw new RuntimeException("Must define a session.");
        }

        if (null != roomId) {
            args.putString(ARG_ROOM_ID, roomId);
        }

        fragment.setArguments(args);
        fragment.setMatrixMessagesListener(listener);
        fragment.setMXSession(session);
        return fragment;
    }

    public interface MatrixMessagesListener {
        void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState);

        void onLiveEventsChunkProcessed();

        void onReceiptEvent(List<String> senderIds);

        void onRoomFlush();

        EventTimeline getEventTimeLine();

        void onTimelineInitialized();

        /**
         * Called when the first batch of messages is loaded.
         */
        void onInitialMessagesLoaded();

        // UI events
        void showInitLoading();

        void hideInitLoading();

        // get the room preview data
        RoomPreviewData getRoomPreviewData();
    }

    // The listener to send messages back
    private MatrixMessagesListener mMatrixMessagesListener;
    // The adapted listener to register to the SDK
    private final IMXEventListener mEventListener = new MXEventListener() {
        @Override
        public void onLiveEventsChunkProcessed(String fromToken, String toToken) {
            if (null != mMatrixMessagesListener) {
                mMatrixMessagesListener.onLiveEventsChunkProcessed();
            }
        }

        @Override
        public void onReceiptEvent(String roomId, List<String> senderIds) {
            if (null != mMatrixMessagesListener) {
                mMatrixMessagesListener.onReceiptEvent(senderIds);
            }
        }

        @Override
        public void onRoomFlush(String roomId) {
            if (null != mMatrixMessagesListener) {
                if (mEventTimeline.isLiveTimeline()) {
                    // clear the history
                    mMatrixMessagesListener.onRoomFlush();

                    // init the timeline
                    mEventTimeline.initHistory();

                    // fill the screen
                    requestInitialHistory();
                }
            }
        }
    };

    private final EventTimeline.EventTimelineListener mEventTimelineListener = new EventTimeline.EventTimelineListener() {
        @Override
        public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) {
            if (null != mMatrixMessagesListener) {
                mMatrixMessagesListener.onEvent(event, direction, roomState);
            }
        }
    };

    private Context mContext;
    private MXSession mSession;
    private Room mRoom;
    public boolean mKeepRoomHistory;

    private EventTimeline mEventTimeline;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d(LOG_TAG, "onCreate");

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(LOG_TAG, "onCreateView");

        View v = super.onCreateView(inflater, container, savedInstanceState);

        // the requests are done in onCreateView  instead of onActivityCreated to speed up in the events request
        // it saves only few ms but it reduces the white screen flash.
        mContext = getActivity().getApplicationContext();

        String roomId = getArguments().getString(ARG_ROOM_ID);

        // this code should never be called
        // but we've got some crashes when the session was null
        // so try to find it from the fragments call stack.
        if (null == mSession) {
            List<Fragment> fragments = null;
            FragmentManager fm = getActivity().getSupportFragmentManager();

            if (null != fm) {
                fragments = fm.getFragments();
            }

            if (null != fragments) {
                for (Fragment fragment : fragments) {
                    if (fragment instanceof MatrixMessageListFragment) {
                        mMatrixMessagesListener = (MatrixMessageListFragment) fragment;
                        mSession = ((MatrixMessageListFragment) fragment).getSession();
                    }
                }
            }
        }

        if (mSession == null) {
            throw new RuntimeException("Must have valid default MXSession.");
        }
        // get the timelime
        if (null == mEventTimeline) {
            mEventTimeline = mMatrixMessagesListener.getEventTimeLine();
        } else {
            mEventTimeline.addEventTimelineListener(mEventTimelineListener);
            // the room has already been initialized
            sendInitialMessagesLoaded();
            return v;
        }

        if (null != mEventTimeline) {
            mEventTimeline.addEventTimelineListener(mEventTimelineListener);
            mRoom = mEventTimeline.getRoom();
        }

        // retrieve the room.
        if (null == mRoom) {
            // check if this room has been joined, if not, join it then get messages.
            mRoom = mSession.getDataHandler().getRoom(roomId);
        }

        // GA reported some weird room content
        // so ensure that the room fields are properly initialized
        mSession.getDataHandler().checkRoom(mRoom);

        // display the message history around a dedicated message
        if ((null != mEventTimeline) && !mEventTimeline.isLiveTimeline()
                && (null != mEventTimeline.getInitialEventId())) {
            initializeTimeline();
        } else {
            boolean joinedRoom = false;
            // does the room already exist ?
            if ((mRoom != null) && (null != mEventTimeline)) {
                // init the history
                mEventTimeline.initHistory();

                // check if some required fields are initialized
                // else, the joining could have been half broken (network error)
                if (null != mRoom.getState().creator) {
                    RoomMember self = mRoom.getMember(mSession.getCredentials().userId);
                    if (self != null && RoomMember.MEMBERSHIP_JOIN.equals(self.membership)) {
                        joinedRoom = true;
                    }
                }

                mRoom.addEventListener(mEventListener);

                // room preview mode
                // i.e display the room messages without joining the room
                if (!mEventTimeline.isLiveTimeline()) {
                    previewRoom();
                }
                // join the room is not yet joined
                else if (!joinedRoom) {
                    Log.d(LOG_TAG, "Joining room >> " + roomId);
                    joinRoom();
                } else {
                    // the room is already joined
                    // fill the messages list
                    requestInitialHistory();
                }
            } else {
                sendInitialMessagesLoaded();
            }
        }

        return v;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if ((null != mRoom) && (null != mEventTimeline)) {
            if (mEventTimeline.isLiveTimeline()) {
                mRoom.removeEventListener(mEventListener);
            }

            mEventTimeline.removeEventTimelineListener(mEventTimelineListener);
        }
    }

    /**
     * Warn the listener that this fragment is ready.
     */
    private void sendInitialMessagesLoaded() {
        final android.os.Handler handler = new android.os.Handler(Looper.getMainLooper());

        // add a delay to avoid calling MatrixListFragment before it is fully initialized
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (null != mMatrixMessagesListener) {
                    mMatrixMessagesListener.onInitialMessagesLoaded();
                }
            }
        }, 100);
    }

    /**
     * Trigger a room preview i.e trigger an initial sync before filling the message list.
     */
    private void previewRoom() {
        Log.d(LOG_TAG, "Make a room preview of " + mRoom.getRoomId());

        if (null != mMatrixMessagesListener) {
            RoomPreviewData roomPreviewData = mMatrixMessagesListener.getRoomPreviewData();

            if (null != roomPreviewData) {
                if (null != roomPreviewData.getRoomResponse()) {
                    Log.d(LOG_TAG, "A preview data is provided with sync response");
                    RoomResponse roomResponse = roomPreviewData.getRoomResponse();

                    // initialize the timeline with the initial sync response
                    RoomSync roomSync = new RoomSync();
                    roomSync.state = new RoomSyncState();
                    roomSync.state.events = roomResponse.state;

                    roomSync.timeline = new RoomSyncTimeline();
                    roomSync.timeline.events = roomResponse.messages.chunk;
                    roomSync.timeline.limited = true;
                    roomSync.timeline.prevBatch = roomResponse.messages.end;

                    mEventTimeline.handleJoinedRoomSync(roomSync, true);

                    Log.d(LOG_TAG, "The room preview is done -> fill the room history");
                    requestInitialHistory();
                } else {
                    Log.d(LOG_TAG,
                            "A preview data is provided with no sync response : assume that it is not possible to get a room preview");

                    if (null != getActivity()) {
                        if (null != mMatrixMessagesListener) {
                            mMatrixMessagesListener.hideInitLoading();
                        }
                    }
                }

                return;
            }
        }

        mSession.getRoomsApiClient().initialSync(mRoom.getRoomId(), new ApiCallback<RoomResponse>() {
            @Override
            public void onSuccess(RoomResponse roomResponse) {
                // initialize the timeline with the initial sync response
                RoomSync roomSync = new RoomSync();
                roomSync.state = new RoomSyncState();
                roomSync.state.events = roomResponse.state;

                roomSync.timeline = new RoomSyncTimeline();
                roomSync.timeline.events = roomResponse.messages.chunk;

                mEventTimeline.handleJoinedRoomSync(roomSync, true);

                Log.d(LOG_TAG, "The room preview is done -> fill the room history");
                requestInitialHistory();
            }

            private void onError(String errorMessage) {
                Log.e(LOG_TAG, "The room preview of " + mRoom.getRoomId() + "failed " + errorMessage);

                if (null != getActivity()) {
                    if (null != mMatrixMessagesListener) {
                        mMatrixMessagesListener.hideInitLoading();
                    }
                }
            }

            @Override
            public void onNetworkError(Exception e) {
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onMatrixError(MatrixError e) {
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onUnexpectedError(Exception e) {
                onError(e.getLocalizedMessage());
            }
        });
    }

    /**
     * Display the InitializeTimeline error.
     *
     * @param error the error
     */
    protected void displayInitializeTimelineError(Object error) {
        String errorMessage = "";

        if (error instanceof MatrixError) {
            errorMessage = ((MatrixError) error).getLocalizedMessage();
        } else if (error instanceof Exception) {
            errorMessage = ((Exception) error).getLocalizedMessage();
        }

        if (!TextUtils.isEmpty(errorMessage)) {
            Log.d(LOG_TAG, "displayInitializeTimelineError : " + errorMessage);
            Toast.makeText(mContext, errorMessage, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Initialize the timeline to fill the screen
     */
    private void initializeTimeline() {
        Log.d(LOG_TAG, "initializeTimeline");

        if (null != mMatrixMessagesListener) {
            mMatrixMessagesListener.showInitLoading();
        }

        mEventTimeline.resetPaginationAroundInitialEvent(30 * 2, new ApiCallback<Void>() {
            @Override
            public void onSuccess(Void info) {
                Log.d(LOG_TAG, "initializeTimeline is done");

                if (null != getActivity()) {
                    if (null != mMatrixMessagesListener) {
                        mMatrixMessagesListener.hideInitLoading();
                        mMatrixMessagesListener.onTimelineInitialized();
                    }
                    sendInitialMessagesLoaded();
                }
            }

            private void onError() {
                Log.d(LOG_TAG, "initializeTimeline fails");

                if (null != getActivity()) {
                    if (null != mMatrixMessagesListener) {
                        mMatrixMessagesListener.hideInitLoading();
                        mMatrixMessagesListener.onTimelineInitialized();
                    }
                }
            }

            @Override
            public void onNetworkError(Exception e) {
                displayInitializeTimelineError(e);
                onError();
            }

            @Override
            public void onMatrixError(MatrixError e) {
                displayInitializeTimelineError(e);
                onError();
            }

            @Override
            public void onUnexpectedError(Exception e) {
                displayInitializeTimelineError(e);
                onError();
            }
        });
    }

    /**
     * Request messages in this room upon entering.
     */
    protected void requestInitialHistory() {

        Log.d(LOG_TAG, "requestInitialHistory " + mRoom.getRoomId());

        // the initial sync will be retrieved when a network connection will be found
        boolean start = backPaginate(new SimpleApiCallback<Integer>(getActivity()) {
            @Override
            public void onSuccess(Integer info) {
                Log.d(LOG_TAG, "requestInitialHistory onSuccess");

                if (null != getActivity()) {
                    if (null != mMatrixMessagesListener) {
                        mMatrixMessagesListener.hideInitLoading();
                        mMatrixMessagesListener.onTimelineInitialized();
                        mMatrixMessagesListener.onInitialMessagesLoaded();
                    }
                }
            }

            private void onError(String errorMessage) {
                Log.e(LOG_TAG, "requestInitialHistory failed" + errorMessage);
                if (null != getActivity()) {
                    Toast.makeText(mContext, errorMessage, Toast.LENGTH_LONG).show();

                    if (null != mMatrixMessagesListener) {
                        mMatrixMessagesListener.hideInitLoading();
                    }
                }
            }

            @Override
            public void onNetworkError(Exception e) {
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onMatrixError(MatrixError e) {
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onUnexpectedError(Exception e) {
                onError(e.getLocalizedMessage());
            }
        });

        if (start) {
            if (null != mMatrixMessagesListener) {
                mMatrixMessagesListener.showInitLoading();
            }
        }
    }

    //==============================================================================================================
    // Setters / getters
    //==============================================================================================================

    /**
     * Set the listener which will be informed of matrix messages. This setter is provided so either
     * a Fragment or an Activity can directly receive callbacks.
     *
     * @param listener the listener for this fragment
     */
    public void setMatrixMessagesListener(MatrixMessagesListener listener) {
        mMatrixMessagesListener = listener;
    }

    /**
     * Set the MX session
     *
     * @param session the session.
     */
    public void setMXSession(MXSession session) {
        mSession = session;
    }

    //==============================================================================================================
    // Room / timeline actions
    //==============================================================================================================

    /**
     * Tells if a back pagination can be done.
     *
     * @return true if a back pagination can be done.
     */
    public boolean canBackPaginate() {
        if (null != mEventTimeline) {
            return mEventTimeline.canBackPaginate();
        } else {
            return false;
        }
    }

    /**
     * Request earlier messages in this room.
     *
     * @param callback the callback
     * @return true if the request is really started
     */
    public boolean backPaginate(ApiCallback<Integer> callback) {
        if (null != mEventTimeline) {
            return mEventTimeline.backPaginate(callback);
        } else {
            return false;
        }
    }

    /**
     * Request the next events in the timeline.
     *
     * @param callback the callback
     * @return true if the request is really started
     */
    public boolean forwardPaginate(ApiCallback<Integer> callback) {
        if ((null != mEventTimeline) && mEventTimeline.isLiveTimeline()) {
            return mEventTimeline.forwardPaginate(callback);
        } else {
            return false;
        }
    }

    /**
     * Send an event in a room
     *
     * @param event    the event
     * @param callback the callback
     */
    public void sendEvent(Event event, ApiCallback<Void> callback) {
        if (null != mRoom) {
            mRoom.sendEvent(event, callback);
        }
    }

    /**
     * Redact an event.
     *
     * @param eventId  the event Id
     * @param callback the callback.
     */
    public void redact(String eventId, ApiCallback<Event> callback) {
        if (null != mRoom) {
            mRoom.redact(eventId, callback);
        }
    }

    /**
     * Join the room.
     */
    private void joinRoom() {
        if (null != mMatrixMessagesListener) {
            mMatrixMessagesListener.showInitLoading();
        }

        Log.d(LOG_TAG, "joinRoom " + mRoom.getRoomId());

        mRoom.join(new SimpleApiCallback<Void>(getActivity()) {
            @Override
            public void onSuccess(Void info) {
                Log.d(LOG_TAG, "joinRoom succeeds");

                if (null != getActivity()) {
                    if (null != mMatrixMessagesListener) {
                        getActivity().runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    mMatrixMessagesListener.hideInitLoading();
                                    mMatrixMessagesListener.onInitialMessagesLoaded();
                                } catch (Exception e) {
                                    Log.e(LOG_TAG, "joinRoom callback fails " + e.getLocalizedMessage());
                                }
                            }
                        });
                    }
                }
            }

            private void onError(String errorMessage) {
                Log.e(LOG_TAG, "joinRoom error: " + errorMessage);
                if (null != getActivity()) {
                    Toast.makeText(mContext, errorMessage, Toast.LENGTH_SHORT).show();
                    if (null != mMatrixMessagesListener) {

                        getActivity().runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mMatrixMessagesListener.hideInitLoading();
                            }
                        });
                    }
                }
            }

            // the request will be automatically restarted when a valid network will be found
            @Override
            public void onNetworkError(Exception e) {
                Log.e(LOG_TAG, "joinRoom Network error: " + e.getLocalizedMessage());
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onMatrixError(MatrixError e) {
                Log.e(LOG_TAG, "joinRoom onMatrixError : " + e.getLocalizedMessage());
                onError(e.getLocalizedMessage());
            }

            @Override
            public void onUnexpectedError(Exception e) {
                Log.e(LOG_TAG, "joinRoom Override : " + e.getLocalizedMessage());
                onError(e.getLocalizedMessage());
            }
        });
    }
}