diff --git a/src/CallHandler.js b/src/CallHandler.js
index 4c68718709..c2ee05b22c 100644
--- a/src/CallHandler.js
+++ b/src/CallHandler.js
@@ -276,6 +276,7 @@ function _onAction(payload) {
                             ).done(function(call) {
                                 placeCall(call);
                             }, function(err) {
+                                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                                 Modal.createDialog(ErrorDialog, {
                                     title: "Failed to set up conference call",
                                     description: "Conference call failed: " + err,
diff --git a/src/MatrixTools.js b/src/MatrixTools.js
deleted file mode 100644
index 3bc7f28e20..0000000000
--- a/src/MatrixTools.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket 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.
-*/
-var CallHandler = require('./CallHandler');
-
-module.exports = {
-    /**
-     * Given a room object, return the alias we should use for it,
-     * if any. This could be the canonical alias if one exists, otherwise
-     * an alias selected arbitrarily but deterministically from the list
-     * of aliases. Otherwise return null;
-     */
-    getDisplayAliasForRoom: function(room) {
-        return room.getCanonicalAlias() || room.getAliases()[0];
-    },
-
-    isDirectMessageRoom: function(room, me, ConferenceHandler, hideConferenceChans) {
-        if (me.membership == "join" || me.membership === "ban" ||
-            (me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey()))
-        {
-            // Used to split rooms via tags
-            var tagNames = Object.keys(room.tags);
-            // Used for 1:1 direct chats
-            var joinedMembers = room.getJoinedMembers();
-
-            // Show 1:1 chats in seperate "Direct Messages" section as long as they haven't
-            // been moved to a different tag section
-            if (joinedMembers.length === 2 && !tagNames.length) {
-                var otherMember = joinedMembers.filter(function(m) {
-                    return m.userId !== me.userId
-                })[0];
-
-                if (ConferenceHandler && ConferenceHandler.isConferenceUser(otherMember.userId)) {
-                    // console.log("Hiding conference 1:1 room %s", room.roomId);
-                    if (!hideConferenceChans) {
-                        return true;
-                    }
-                } else {
-                    return true;
-                }
-            }
-        }
-        return false;
-    },
-}
-
diff --git a/src/Rooms.js b/src/Rooms.js
new file mode 100644
index 0000000000..7f4564b439
--- /dev/null
+++ b/src/Rooms.js
@@ -0,0 +1,77 @@
+/*
+Copyright 2015, 2016 OpenMarket 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.
+*/
+
+
+/**
+ * Given a room object, return the alias we should use for it,
+ * if any. This could be the canonical alias if one exists, otherwise
+ * an alias selected arbitrarily but deterministically from the list
+ * of aliases. Otherwise return null;
+ */
+export function getDisplayAliasForRoom(room) {
+    return room.getCanonicalAlias() || room.getAliases()[0];
+}
+
+/**
+ * If the room contains only two members including the logged-in user,
+ * return the other one. Otherwise, return null.
+ */
+export function getOnlyOtherMember(room, me) {
+    const joinedMembers = room.getJoinedMembers();
+
+    if (joinedMembers.length === 2) {
+        return joinedMembers.filter(function(m) {
+            return m.userId !== me.userId
+        })[0];
+    }
+
+    return null;
+}
+
+export function isConfCallRoom(room, me, conferenceHandler) {
+    if (!conferenceHandler) return false;
+
+    if (me.membership != "join") {
+        return false;
+    }
+
+    const otherMember = getOnlyOtherMember(room, me);
+    if (otherMember === null) {
+        return false;
+    }
+
+    if (conferenceHandler.isConferenceUser(otherMember.userId)) {
+        return true;
+    }
+}
+
+export function looksLikeDirectMessageRoom(room, me) {
+    if (me.membership == "join" || me.membership === "ban" ||
+        (me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey()))
+    {
+        // Used to split rooms via tags
+        const tagNames = Object.keys(room.tags);
+        // Used for 1:1 direct chats
+        const joinedMembers = room.getJoinedMembers();
+
+        // Show 1:1 chats in seperate "Direct Messages" section as long as they haven't
+        // been moved to a different tag section
+        if (joinedMembers.length === 2 && !tagNames.length) {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/src/SlashCommands.js b/src/SlashCommands.js
index 759a95c8ff..be007496dd 100644
--- a/src/SlashCommands.js
+++ b/src/SlashCommands.js
@@ -15,7 +15,6 @@ limitations under the License.
 */
 
 var MatrixClientPeg = require("./MatrixClientPeg");
-var MatrixTools = require("./MatrixTools");
 var dis = require("./dispatcher");
 var Tinter = require("./Tinter");
 
diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js
index 39cf1179d7..ac7f1b418a 100644
--- a/src/autocomplete/RoomProvider.js
+++ b/src/autocomplete/RoomProvider.js
@@ -4,7 +4,7 @@ import Q from 'q';
 import MatrixClientPeg from '../MatrixClientPeg';
 import Fuse from 'fuse.js';
 import {PillCompletion} from './Components';
-import {getDisplayAliasForRoom} from '../MatrixTools';
+import {getDisplayAliasForRoom} from '../Rooms';
 import sdk from '../index';
 
 const ROOM_REGEX = /(?=#)([^\s]*)/g;
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 48431a956b..c83da2b8f0 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -36,7 +36,7 @@ var PostRegistration = require("./login/PostRegistration");
 var Modal = require("../../Modal");
 var Tinter = require("../../Tinter");
 var sdk = require('../../index');
-var MatrixTools = require('../../MatrixTools');
+var Rooms = require('../../Rooms');
 var linkifyMatrix = require("../../linkify-matrix");
 var KeyCode = require('../../KeyCode');
 var Lifecycle = require('../../Lifecycle');
@@ -482,7 +482,7 @@ module.exports = React.createClass({
             var presentedId = room_info.room_alias || room_info.room_id;
             var room = MatrixClientPeg.get().getRoom(room_info.room_id);
             if (room) {
-                var theAlias = MatrixTools.getDisplayAliasForRoom(room);
+                var theAlias = Rooms.getDisplayAliasForRoom(room);
                 if (theAlias) presentedId = theAlias;
 
                 // No need to do this given RoomView triggers it itself...
@@ -602,7 +602,7 @@ module.exports = React.createClass({
                 var presentedId = self.state.currentRoomId;
                 var room = MatrixClientPeg.get().getRoom(self.state.currentRoomId);
                 if (room) {
-                    var theAlias = MatrixTools.getDisplayAliasForRoom(room);
+                    var theAlias = Rooms.getDisplayAliasForRoom(room);
                     if (theAlias) presentedId = theAlias;
                 }
 
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 3d0bcf7445..041493d420 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -36,7 +36,6 @@ var dis = require("../../dispatcher");
 var Tinter = require("../../Tinter");
 var rate_limited_func = require('../../ratelimitedfunc');
 var ObjectUtils = require('../../ObjectUtils');
-var MatrixTools = require('../../MatrixTools');
 
 import UserProvider from '../../autocomplete/UserProvider';
 
diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index 91a32f51ac..8b439fcfd7 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -25,7 +25,8 @@ var Unread = require('../../../Unread');
 var dis = require("../../../dispatcher");
 var sdk = require('../../../index');
 var rate_limited_func = require('../../../ratelimitedfunc');
-var MatrixTools = require('../../../MatrixTools');
+var Rooms = require('../../../Rooms');
+var DMRoomMap = require('../../../utils/DMRoomMap');
 
 var HIDE_CONFERENCE_CHANS = true;
 
@@ -207,8 +208,10 @@ module.exports = React.createClass({
         s.lists["m.lowpriority"] = [];
         s.lists["im.vector.fake.archived"] = [];
 
+        const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
+
         MatrixClientPeg.get().getRooms().forEach(function(room) {
-            var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+            const me = room.getMember(MatrixClientPeg.get().credentials.userId);
             if (!me) return;
 
             // console.log("room = " + room.name + ", me.membership = " + me.membership +
@@ -219,7 +222,10 @@ module.exports = React.createClass({
             if (me.membership == "invite") {
                 s.lists["im.vector.fake.invite"].push(room);
             }
-            else if (MatrixTools.isDirectMessageRoom(room, me, self.props.ConferenceHandler, HIDE_CONFERENCE_CHANS)) {
+            else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
+                // skip past this room & don't put it in any lists
+            }
+            else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
                 // "Direct Message" rooms
                 s.lists["im.vector.fake.direct"].push(room);
             }
@@ -248,6 +254,38 @@ module.exports = React.createClass({
             }
         });
 
+        if (s.lists["im.vector.fake.direct"].length == 0 && MatrixClientPeg.get().getAccountData('m.direct') === undefined) {
+            // scan through the 'recents' list for any rooms which look like DM rooms
+            // and make them DM rooms
+            const oldRecents = s.lists["im.vector.fake.recent"];
+            s.lists["im.vector.fake.recent"] = [];
+
+            for (const room of oldRecents) {
+                const me = room.getMember(MatrixClientPeg.get().credentials.userId);
+
+                if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
+                    s.lists["im.vector.fake.direct"].push(room);
+                } else {
+                    s.lists["im.vector.fake.recent"].push(room);
+                }
+            }
+
+            // save these new guessed DM rooms into the account data
+            const newMDirectEvent = {};
+            for (const room of s.lists["im.vector.fake.direct"]) {
+                const me = room.getMember(MatrixClientPeg.get().credentials.userId);
+                const otherPerson = Rooms.getOnlyOtherMember(room, me);
+                if (!otherPerson) continue;
+
+                const roomList = newMDirectEvent[otherPerson.userId] || [];
+                roomList.push(room.roomId);
+                newMDirectEvent[otherPerson.userId] = roomList;
+            }
+
+            // if this fails, fine, we'll just do the same thing next time we get the room lists
+            MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
+        }
+
         //console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
 
         // we actually apply the sorting to this when receiving the prop in RoomSubLists.
diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js
new file mode 100644
index 0000000000..d92ae87e64
--- /dev/null
+++ b/src/utils/DMRoomMap.js
@@ -0,0 +1,46 @@
+/*
+Copyright 2016 OpenMarket 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.
+*/
+
+/**
+ * Class that takes a Matrix Client and flips the m.direct map
+ * so the operation of mapping a room ID to which user it's a DM
+ * with can be performed efficiently.
+ */
+export default class DMRoomMap {
+    constructor(matrixClient) {
+        const mDirectEvent = matrixClient.getAccountData('m.direct');
+        if (!mDirectEvent) {
+            this.userToRooms = {};
+            this.roomToUser = {};
+        } else {
+            this.userToRooms = mDirectEvent.getContent();
+            this.roomToUser = {};
+            for (const user of Object.keys(this.userToRooms)) {
+                for (const roomId of this.userToRooms[user]) {
+                    this.roomToUser[roomId] = user;
+                }
+            }
+        }
+    }
+
+    getDMRoomsForUserId(userId) {
+        return this.userToRooms[userId];
+    }
+
+    getUserIdForRoomId(roomId) {
+        return this.roomToUser[roomId];
+    }
+}