Bug 726382 - Fold the GeckoDirProvider and a bunch of duplicated/complicated code into a new GeckoProfile class. r=wesj
authorKartikaya Gupta <kgupta@mozilla.com>
Sat, 25 Feb 2012 23:22:40 -0500
changeset 87756 92362d0c00e4ae87ce9cc07435ea2b7542328ef1
parent 87755 0fb823decef1b362e789bfeb55a4170e7455c9f4
child 87757 0dc734db4a13cd9b29cf4cec4b88d1e6c3f8de09
push id22143
push userphilringnalda@gmail.com
push dateSun, 26 Feb 2012 23:12:35 +0000
treeherdermozilla-central@b98fc24ac54b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs726382
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 726382 - Fold the GeckoDirProvider and a bunch of duplicated/complicated code into a new GeckoProfile class. r=wesj
mobile/android/base/AboutHomeContent.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoDirProvider.java
mobile/android/base/GeckoProfile.java
mobile/android/base/LauncherShortcuts.java.in
mobile/android/base/Makefile.in
mobile/android/base/db/BrowserProvider.java.in
mobile/android/base/db/PasswordsProvider.java.in
--- a/mobile/android/base/AboutHomeContent.java
+++ b/mobile/android/base/AboutHomeContent.java
@@ -362,35 +362,16 @@ public class AboutHomeContent extends Sc
         if (mTopSitesGrid != null) 
             mTopSitesGrid.setNumColumns(getNumberOfColumns());
         if (mTopSitesAdapter != null)
             mTopSitesAdapter.notifyDataSetChanged();
 
         super.onConfigurationChanged(newConfig);
     }
 
-    private String readJSONFile(Activity activity, String filename) {
-        InputStream fileStream = null;
-        File profileDir = GeckoApp.mAppContext.getProfileDir();
-
-        if (profileDir == null)
-            return null;
-
-        File recommendedAddonsFile = new File(profileDir, filename);
-        if (recommendedAddonsFile.exists()) {
-            try {
-                fileStream = new FileInputStream(recommendedAddonsFile);
-            } catch (FileNotFoundException fnfe) {}
-        }
-        if (fileStream == null)
-            return null;
-
-        return readStringFromStream(fileStream);
-    }
-
     private String readFromZipFile(Activity activity, String filename) {
         ZipFile zip = null;
         String str = null;
         try {
             InputStream fileStream = null;
             File applicationPackage = new File(activity.getApplication().getPackageResourcePath());
             zip = new ZipFile(applicationPackage);
             if (zip == null)
@@ -454,19 +435,21 @@ public class AboutHomeContent extends Sc
             // Defaults to pageUrl = iconUrl in case of error
         }
 
         return pageUrl;
     }
 
     private void readRecommendedAddons(final Activity activity) {
         final String addonsFilename = "recommended-addons.json";
-        String jsonString = readJSONFile(activity, addonsFilename);
-        if (jsonString == null) {
-            Log.i("Addons", "filestream is null");
+        String jsonString;
+        try {
+            jsonString = GeckoApp.mAppContext.getProfile().readFile(addonsFilename);
+        } catch (IOException ioe) {
+            Log.i(LOGTAG, "filestream is null");
             jsonString = readFromZipFile(activity, addonsFilename);
         }
 
         JSONArray addonsArray = null;
         if (jsonString != null) {
             try {
                 addonsArray = new JSONObject(jsonString).getJSONArray("addons");
             } catch (JSONException e) {
@@ -519,43 +502,23 @@ public class AboutHomeContent extends Sc
                 } catch (JSONException e) {
                     Log.i(LOGTAG, "error reading json file", e);
                 }
             }
         });
     }
 
     private void readLastTabs(final Activity activity) {
-        final String sessionFilename;
-        if (!GeckoApp.sIsGeckoReady) {
-            File profileDir = GeckoApp.mAppContext.getProfileDir();
-            if (profileDir == null)
-                return;
-
-            if (new File(profileDir, "sessionstore.js").exists()) {
-                // we crashed, so sessionstore.js has tabs from last time
-                sessionFilename = "sessionstore.js";
-            } else if (new File(profileDir, "sessionstore.bak").exists()) {
-                // we did not crash, so previous session was moved to sessionstore.bak on quit
-                sessionFilename = "sessionstore.bak";
-            } else {
-                // no previous session data
-                return;
-            }
-        } else {
-            // sessionstore init has occurred, so previous session will always
-            // be in sessionstore.bak
-            sessionFilename = "sessionstore.bak";
+        String jsonString = GeckoApp.mAppContext.getProfile().readSessionFile(GeckoApp.sIsGeckoReady);
+        if (jsonString == null) {
+            // no previous session data
+            return;
         }
 
         final JSONArray tabs;
-        String jsonString = readJSONFile(activity, sessionFilename);
-        if (jsonString == null)
-            return;
-
         try {
             tabs = new JSONObject(jsonString).getJSONArray("windows")
                                              .getJSONObject(0)
                                              .getJSONArray("tabs");
         } catch (JSONException e) {
             Log.i(LOGTAG, "error reading json file", e);
             return;
         }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -120,17 +120,17 @@ abstract public class GeckoApp
     private RelativeLayout mGeckoLayout;
     public static SurfaceView cameraView;
     public static GeckoApp mAppContext;
     public static boolean mDOMFullScreen = false;
     public static File sGREDir = null;
     public static Menu sMenu;
     private static GeckoThread sGeckoThread = null;
     public GeckoAppHandler mMainHandler;
-    private File mProfileDir;
+    private GeckoProfile mProfile;
     public static boolean sIsGeckoReady = false;
     public static int mOrientation;
 
     private IntentFilter mConnectivityFilter;
 
     private BroadcastReceiver mConnectivityReceiver;
     private BroadcastReceiver mBatteryReceiver;
 
@@ -844,31 +844,16 @@ abstract public class GeckoApp
                 settings.edit().putString(keyName, appVersion).commit();
 
             Log.i(LOGTAG, "Startup mode: " + mStartupMode);
 
             return mStartupMode;
         }
     }
 
-    public File getProfileDir() {
-        return getProfileDir("default");
-    }
-
-    public File getProfileDir(final String profileName) {
-        if (mProfileDir != null)
-            return mProfileDir;
-        try {
-            mProfileDir = GeckoDirProvider.getProfileDir(mAppContext, profileName);
-        } catch (IOException ex) {
-            Log.e(LOGTAG, "Error getting profile dir.", ex);
-        }
-        return mProfileDir;
-    }
-
     void addTab() {
         showAwesomebar(AwesomeBar.Type.ADD);
     }
 
     void showTabs() {
         Intent intent = new Intent(mAppContext, TabsTray.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
         startActivity(intent);
@@ -1709,17 +1694,17 @@ abstract public class GeckoApp
         mInitialized = true;
 
         Intent intent = getIntent();
         String args = intent.getStringExtra("args");
         if (args != null && args.contains("-profile")) {
             Pattern p = Pattern.compile("(?:-profile\\s*)(\\w*)(\\s*)");
             Matcher m = p.matcher(args);
             if (m.find()) {
-                mProfileDir = new File(m.group(1));
+                mProfile = GeckoProfile.get(this, m.group(1));
                 mLastTitle = null;
                 mLastViewport = null;
                 mLastScreen = null;
             }
         }
 
         if (ACTION_UPDATE.equals(intent.getAction()) || args != null && args.contains("-alert update-app")) {
             Log.i(LOGTAG,"onCreate: Update request");
@@ -1731,23 +1716,17 @@ abstract public class GeckoApp
 
         String passedUri = null;
         String uri = getURIFromIntent(intent);
         if (uri != null && uri.length() > 0)
             passedUri = mLastTitle = uri;
 
         if (passedUri == null || passedUri.equals("about:home")) {
             // show about:home if we aren't restoring previous session
-            Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - start check sessionstore.js exists");
-            File profileDir = getProfileDir();
-            boolean sessionExists = false;
-            if (profileDir != null)
-                sessionExists = new File(profileDir, "sessionstore.js").exists();
-            Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - finish check sessionstore.js exists");
-            if (!sessionExists) {
+            if (! getProfile().hasSession()) {
                 mBrowserToolbar.updateTabCount(1);
                 showAboutHome();
             }
         } else {
             mBrowserToolbar.updateTabCount(1);
         }
 
         if (sGREDir == null)
@@ -1887,16 +1866,24 @@ abstract public class GeckoApp
                     return;
                 }
 
                 checkMigrateProfile();
             }
         }, 50);
     }
 
+    public GeckoProfile getProfile() {
+        // fall back to default profile if we didn't load a specific one
+        if (mProfile == null) {
+            mProfile = GeckoProfile.get(this);
+        }
+        return mProfile;
+    }
+
     /**
      * Enable Android StrictMode checks (for supported OS versions).
      * http://developer.android.com/reference/android/os/StrictMode.html
      */
     private void enableStrictMode()
     {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
             return;
@@ -2366,17 +2353,17 @@ abstract public class GeckoApp
             reader.close();
         } catch (Exception e) {
             Log.i(LOGTAG, "error reading update status", e);
         }
         return status;
     }
 
     private void checkMigrateProfile() {
-        File profileDir = getProfileDir();
+        File profileDir = getProfile().getDir();
         long currentTime = SystemClock.uptimeMillis();
 
         if (profileDir != null) {
             Log.i(LOGTAG, "checking profile migration in: " + profileDir.getAbsolutePath());
             final GeckoApp app = GeckoApp.mAppContext;
             final SetupScreen setupScreen = new SetupScreen(app);
             // don't show unless we take a while
             setupScreen.showDelayed(mMainHandler);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -41,17 +41,16 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.*;
-import java.nio.channels.*;
 import java.text.*;
 import java.util.*;
 import java.util.zip.*;
 import java.util.concurrent.*;
 
 import android.os.*;
 import android.app.*;
 import android.text.*;
@@ -258,130 +257,39 @@ public class GeckoAppShell
                 }
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "exception while stating cache dir: ", e);
         }
         return sFreeSpace;
     }
 
-    static boolean moveFile(File inFile, File outFile)
-    {
-        Log.i(LOGTAG, "moving " + inFile + " to " + outFile);
-        if (outFile.isDirectory())
-            outFile = new File(outFile, inFile.getName());
-        try {
-            if (inFile.renameTo(outFile))
-                return true;
-        } catch (SecurityException se) {
-            Log.w(LOGTAG, "error trying to rename file", se);
-        }
-        try {
-            long lastModified = inFile.lastModified();
-            outFile.createNewFile();
-            // so copy it instead
-            FileChannel inChannel = new FileInputStream(inFile).getChannel();
-            FileChannel outChannel = new FileOutputStream(outFile).getChannel();
-            long size = inChannel.size();
-            long transferred = inChannel.transferTo(0, size, outChannel);
-            inChannel.close();
-            outChannel.close();
-            outFile.setLastModified(lastModified);
-
-            if (transferred == size)
-                inFile.delete();
-            else
-                return false;
-        } catch (Exception e) {
-            Log.e(LOGTAG, "exception while moving file: ", e);
-            try {
-                outFile.delete();
-            } catch (SecurityException se) {
-                Log.w(LOGTAG, "error trying to delete file", se);
-            }
-            return false;
-        }
-        return true;
-    }
-
-    static boolean moveDir(File from, File to) {
-        try {
-            to.mkdirs();
-            if (from.renameTo(to))
-                return true;
-        } catch (SecurityException se) {
-            Log.w(LOGTAG, "error trying to rename file", se);
-        }
-        File[] files = from.listFiles();
-        boolean retVal = true;
-        if (files == null)
-            return false;
-        try {
-            Iterator<File> fileIterator = Arrays.asList(files).iterator();
-            while (fileIterator.hasNext()) {
-                File file = fileIterator.next();
-                File dest = new File(to, file.getName());
-                if (file.isDirectory())
-                    retVal = moveDir(file, dest) ? retVal : false;
-                else
-                    retVal = moveFile(file, dest) ? retVal : false;
-            }
-            from.delete();
-        } catch(Exception e) {
-            Log.e(LOGTAG, "error trying to move file", e);
-        }
-        return retVal;
-    }
-
     // java-side stuff
     public static boolean loadLibsSetup(String apkName) {
         // The package data lib directory isn't placed in ld.so's
         // search path, so we have to manually load libraries that
         // libxul will depend on.  Not ideal.
         GeckoApp geckoApp = GeckoApp.mAppContext;
-        String homeDir;
-        sHomeDir = GeckoDirProvider.getFilesDir(geckoApp);
-        homeDir = sHomeDir.getPath();
-
-        // handle the application being moved to phone from sdcard
-        File profileDir = new File(homeDir, "mozilla");
-        File oldHome = new File("/data/data/" + 
-                    GeckoApp.mAppContext.getPackageName() + "/mozilla");
-        if (oldHome.exists())
-            moveDir(oldHome, profileDir);
+        GeckoProfile profile = geckoApp.getProfile();
+        profile.moveProfilesToAppInstallLocation();
 
-        if (Build.VERSION.SDK_INT < 8 ||
-            geckoApp.getApplication().getPackageResourcePath().startsWith("/data") ||
-            geckoApp.getApplication().getPackageResourcePath().startsWith("/system")) {
-            if (Build.VERSION.SDK_INT >= 8) {
-                File extHome =  geckoApp.getExternalFilesDir(null);
-                File extProf = new File (extHome, "mozilla");
-                if (extHome != null && extProf != null && extProf.exists())
-                    moveDir(extProf, profileDir);
-            }
-        } else {
-            File intHome =  geckoApp.getFilesDir();
-            File intProf = new File(intHome, "mozilla");
-            if (intHome != null && intProf != null && intProf.exists())
-                moveDir(intProf, profileDir);
-        }
         try {
             String[] dirs = GeckoApp.mAppContext.getPluginDirectories();
             StringBuffer pluginSearchPath = new StringBuffer();
             for (int i = 0; i < dirs.length; i++) {
                 Log.i(LOGTAG, "dir: " + dirs[i]);
                 pluginSearchPath.append(dirs[i]);
                 pluginSearchPath.append(":");
             }
             GeckoAppShell.putenv("MOZ_PLUGIN_PATH="+pluginSearchPath);
         } catch (Exception ex) {
             Log.i(LOGTAG, "exception getting plugin dirs", ex);
         }
 
-        GeckoAppShell.putenv("HOME=" + homeDir);
+        GeckoAppShell.putenv("HOME=" + profile.getFilesDir().getPath());
         GeckoAppShell.putenv("GRE_HOME=" + GeckoApp.sGREDir.getPath());
         Intent i = geckoApp.getIntent();
         String env = i.getStringExtra("env0");
         Log.i(LOGTAG, "env0: "+ env);
         for (int c = 1; env != null; c++) {
             GeckoAppShell.putenv(env);
             env = i.getStringExtra("env" + c);
             Log.i(LOGTAG, "env"+ c +": "+ env);
deleted file mode 100644
--- a/mobile/android/base/GeckoDirProvider.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (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.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Wes Johnston <wjohnston@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-package org.mozilla.gecko;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.lang.Void;
-import java.util.Date;
-import java.util.Random;
-import java.util.Map;
-import java.util.HashMap;
-
-abstract public class GeckoDirProvider
-{
-    private static final String LOGTAG = "GeckoDirProvider";
-    private static HashMap<String, File> mProfileDirs = new HashMap<String, File>();
-
-    /**
-     * Get the default Mozilla profile directory for a given Activity instance.
-     *
-     * @param aContext
-     *        The context for the activity. Must not be null
-     * @return
-     *       The profile directory.
-     */
-    static public File getProfileDir(final Context aContext)
-            throws IllegalArgumentException, IOException {
-        // XXX: TO-DO read profiles.ini to get the default profile. bug 71530
-        return getProfileDir(aContext, "default");
-    }
-
-    /**
-     * Get a particular profile directory for a given Activity.
-     * If no profile directory currently exists, will create and return a profile directory.
-     * Otherwise will return null;
-     *
-     * @param aContext
-     *        The context for the Activity we want a profile for. Must not be null.
-     * @param aProfileName
-     *        The name of the profile to open. Must be a non-empty string
-     * @return
-     *       The profile directory.
-     */
-    static public File getProfileDir(final Context aContext, final String aProfileName)
-            throws IllegalArgumentException, IOException {
-
-        if (aContext == null)
-            throw new IllegalArgumentException("Must provide a valid context");
-
-        if (aProfileName == null || aProfileName.trim().equals(""))
-            throw new IllegalArgumentException("Profile name: '" + aProfileName + "' is not valid");
-
-        Log.i(LOGTAG, "Get profile dir for " + aProfileName);
-        synchronized (mProfileDirs) {
-            File profileDir = mProfileDirs.get(aProfileName);
-            if (profileDir != null)
-                return profileDir;
-
-            // we do not want to call File.exists on startup, so we first don't
-            // attempt to create the mozilla directory.
-            File mozDir = GeckoDirProvider.ensureMozillaDirectory(aContext);
-            profileDir = GeckoDirProvider.getProfileDir(mozDir, aProfileName);
-
-            if (profileDir == null) {
-                // Throws if cannot create.
-                profileDir = GeckoDirProvider.createProfileDir(mozDir, aProfileName);
-            }
-            mProfileDirs.put(aProfileName, profileDir);
-            return profileDir;
-        }
-    }
-
-    private static File getProfileDir(final File aRoot, final String aProfileName)
-            throws IllegalArgumentException {
-        if (aRoot == null)
-            throw new IllegalArgumentException("Invalid root directory");
-
-        File[] profiles = aRoot.listFiles(new FileFilter() {
-            public boolean accept(File pathname) {
-                return pathname.getName().endsWith("." + aProfileName);
-            }
-        });
-
-        if (profiles != null && profiles.length > 0)
-            return profiles[0];
-        return null;
-    }
-
-    private static File ensureMozillaDirectory(final Context aContext)
-            throws IOException, IllegalArgumentException {
-        if (aContext == null)
-            throw new IllegalArgumentException("Must provide a valid context");
-        File filesDir = GeckoDirProvider.getFilesDir(aContext);
-
-        File mozDir = new File(filesDir, "mozilla");
-        if (!mozDir.exists()) {
-            if (!mozDir.mkdir())
-                throw new IOException("Unable to create mozilla directory at " + mozDir.getPath());
-        }
-        return mozDir;
-    }
-
-    static final char kTable[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
-                                   'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-                                   '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };
-
-    private static File createProfileDir(final File aRootDir, final String aProfileName)
-            throws IOException, IllegalArgumentException {
-
-        if (aRootDir == null)
-            throw new IllegalArgumentException("Must provide a valid root directory");
-
-        if (aProfileName == null || aProfileName.trim().equals(""))
-            throw new IllegalArgumentException("Profile name: '" + aProfileName + "' is not valid");
-
-        // XXX: TO-DO If we already have an ini file, we should append the
-        //      new profile information to it. For now we just throw an exception.
-        //      see bug 715391
-        final File profileIni = new File(aRootDir, "profiles.ini");
-        if (profileIni.exists())
-            throw new IOException("Can't create new profiles");
-
-        String saltedName = saltProfileName(aProfileName);
-        File profile = new File(aRootDir, saltedName);
-        while (profile.exists()) {
-            saltedName = saltProfileName(aProfileName);
-            profile = new File(aRootDir, saltedName);
-        }
-
-        if (!profile.mkdir()) 
-            throw new IOException("Unable to create profile at " + profile.getPath());
-
-        Log.i(LOGTAG, "Creating new profile at " + profile.getPath());
-        final String fSaltedName = saltedName;
-
-        FileWriter outputStream = new FileWriter(profileIni, true);
-        outputStream.write("[General]\n" +
-                           "StartWithLastProfile=1\n" +
-                           "\n" +
-                           "[Profile0]\n" +
-                           "Name=" + aProfileName + "\n" +
-                           "IsRelative=1\n" +
-                           "Path=" + fSaltedName + "\n" +
-                           "Default=1\n");
-        outputStream.close();
-
-        return profile;
-    }
-
-    private static String saltProfileName(final String aName) {
-        Random randomGenerator = new Random(System.nanoTime());
-
-        StringBuilder salt = new StringBuilder();
-        int i;
-        for (i = 0; i < 8; ++i)
-            salt.append(kTable[randomGenerator.nextInt(kTable.length)]);
-
-        salt.append(".");
-        return salt.append(aName).toString();
-    }
-
-    public static File getFilesDir(final Context aContext) {
-        if (aContext == null)
-            throw new IllegalArgumentException("Must provide a valid context");
-
-        if (Build.VERSION.SDK_INT < 8 ||
-            aContext.getPackageResourcePath().startsWith("/data") ||
-            aContext.getPackageResourcePath().startsWith("/system")) {
-            return aContext.getFilesDir();
-        }
-        return aContext.getExternalFilesDir(null);
-    }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoProfile.java
@@ -0,0 +1,356 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.HashMap;
+import android.content.Context;
+import android.os.Build;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+
+public final class GeckoProfile {
+    private static final String LOGTAG = "GeckoProfile";
+
+    private static HashMap<String, GeckoProfile> sProfileCache = new HashMap<String, GeckoProfile>();
+
+    private final Context mContext;
+    private final String mName;
+    private File mMozDir;
+    private File mDir;
+
+    public static GeckoProfile get(Context context) {
+        return get(context, null);
+    }
+
+    public static GeckoProfile get(Context context, String profileName) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must be non-null");
+        }
+        if (TextUtils.isEmpty(profileName)) {
+            // XXX: TO-DO read profiles.ini to get the default profile. bug 715307
+            profileName = "default";
+        }
+
+        synchronized (sProfileCache) {
+            GeckoProfile profile = sProfileCache.get(profileName);
+            if (profile == null) {
+                profile = new GeckoProfile(context, profileName);
+                sProfileCache.put(profileName, profile);
+            }
+            return profile;
+        }
+    }
+
+    private GeckoProfile(Context context, String profileName) {
+        mContext = context;
+        mName = profileName;
+    }
+
+    public File getDir() {
+        if (mDir != null) {
+            return mDir;
+        }
+
+        try {
+            File mozillaDir = ensureMozillaDirectory();
+            mDir = findProfileDir(mozillaDir);
+            if (mDir == null) {
+                mDir = createProfileDir(mozillaDir);
+            } else {
+                Log.d(LOGTAG, "Found profile dir: " + mDir.getAbsolutePath());
+            }
+        } catch (IOException ioe) {
+            Log.e(LOGTAG, "Error getting profile dir", ioe);
+        }
+        return mDir;
+    }
+
+    public boolean hasSession() {
+        Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - start check sessionstore.js exists");
+        File dir = getDir();
+        boolean hasSession = (dir != null && new File(dir, "sessionstore.js").exists());
+        Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - finish check sessionstore.js exists");
+        return hasSession;
+    }
+
+    public String readSessionFile(boolean geckoReady) {
+        File dir = getDir();
+        if (dir == null) {
+            return null;
+        }
+
+        File sessionFile = null;
+        if (! geckoReady) {
+            // we might have crashed, in which case sessionstore.js has tabs from last time
+            sessionFile = new File(dir, "sessionstore.js");
+            if (! sessionFile.exists()) {
+                sessionFile = null;
+            }
+        }
+        if (sessionFile == null) {
+            // either we did not crash, so previous session was moved to sessionstore.bak on quit,
+            // or sessionstore init has occurred, so previous session will always
+            // be in sessionstore.bak
+            sessionFile = new File(dir, "sessionstore.bak");
+            // no need to check if the session file exists here; readFile will throw
+            // an IOException if it does not
+        }
+
+        try {
+            return readFile(sessionFile);
+        } catch (IOException ioe) {
+            Log.i(LOGTAG, "Unable to read session file " + sessionFile.getAbsolutePath());
+            return null;
+        }
+    }
+
+    public String readFile(String filename) throws IOException {
+        File dir = getDir();
+        if (dir == null) {
+            throw new IOException("No profile directory found");
+        }
+        File target = new File(dir, filename);
+        return readFile(target);
+    }
+
+    private String readFile(File target) throws IOException {
+        FileReader fr = new FileReader(target);
+        try {
+            StringBuffer sb = new StringBuffer();
+            char[] buf = new char[8192];
+            int read = fr.read(buf);
+            while (read >= 0) {
+                sb.append(buf, 0, read);
+                read = fr.read(buf);
+            }
+            return sb.toString();
+        } finally {
+            fr.close();
+        }
+    }
+
+    public File getFilesDir() {
+        if (isOnInternalStorage()) {
+            return mContext.getFilesDir();
+        } else {
+            return mContext.getExternalFilesDir(null);
+        }
+    }
+
+    private boolean isOnInternalStorage() {
+        // prior to version 8, apps were always on internal storage
+        if (Build.VERSION.SDK_INT < 8) {
+            return true;
+        }
+        // otherwise, check app install location to see if it is on internal storage
+        String resourcePath = mContext.getPackageResourcePath();
+        if (resourcePath.startsWith("/data") || resourcePath.startsWith("/system")) {
+            return true;
+        }
+
+        // otherwise we're most likely on external storage
+        return false;
+    }
+
+    public void moveProfilesToAppInstallLocation() {
+        // check normal install directory
+        moveProfilesFrom(new File("/data/data/" + mContext.getPackageName()));
+
+        if (isOnInternalStorage()) {
+            if (Build.VERSION.SDK_INT >= 8) {
+                // if we're currently on internal storage, but we're on API >= 8, so it's possible that
+                // we were previously on external storage, check there for profiles to pull in
+                moveProfilesFrom(mContext.getExternalFilesDir(null));
+            }
+        } else {
+            // we're currently on external storage, but could have been on internal storage previously,
+            // so pull in those profiles
+            moveProfilesFrom(mContext.getFilesDir());
+        }
+    }
+
+    private void moveProfilesFrom(File oldFilesDir) {
+        if (oldFilesDir == null) {
+            return;
+        }
+        File oldMozDir = new File(oldFilesDir, "mozilla");
+        if (! (oldMozDir.exists() && oldMozDir.isDirectory())) {
+            return;
+        }
+
+        // if we get here, we know that oldMozDir exists
+
+        File currentMozDir;
+        try {
+            currentMozDir = ensureMozillaDirectory();
+            if (currentMozDir.equals(oldMozDir)) {
+                return;
+            }
+        } catch (IOException ioe) {
+            Log.e(LOGTAG, "Unable to create a profile directory!", ioe);
+            return;
+        }
+
+        Log.d(LOGTAG, "Moving old profile directories from " + oldMozDir.getAbsolutePath());
+
+        // if we get here, we know that oldMozDir != currentMozDir, so we have some stuff to move
+        moveDirContents(oldMozDir, currentMozDir);
+    }
+
+    private void moveDirContents(File src, File dst) {
+        File[] files = src.listFiles();
+        if (files == null) {
+            src.delete();
+            return;
+        }
+        for (File f : files) {
+            File target = new File(dst, f.getName());
+            try {
+                if (f.renameTo(target)) {
+                    continue;
+                }
+            } catch (SecurityException se) {
+                Log.e(LOGTAG, "Unable to rename file to " + target.getAbsolutePath() + " while moving profiles", se);
+            }
+            // rename failed, try moving manually
+            if (f.isDirectory()) {
+                if (target.mkdirs()) {
+                    moveDirContents(f, target);
+                } else {
+                    Log.e(LOGTAG, "Unable to create folder " + target.getAbsolutePath() + " while moving profiles");
+                }
+            } else {
+                if (! moveFile(f, target)) {
+                    Log.e(LOGTAG, "Unable to move file " + target.getAbsolutePath() + " while moving profiles");
+                }
+            }
+        }
+        src.delete();
+    }
+
+    private boolean moveFile(File src, File dst) {
+        boolean success = false;
+        long lastModified = src.lastModified();
+        try {
+            FileInputStream fis = new FileInputStream(src);
+            try {
+                FileOutputStream fos = new FileOutputStream(dst);
+                try {
+                    FileChannel inChannel = fis.getChannel();
+                    long size = inChannel.size();
+                    if (size == inChannel.transferTo(0, size, fos.getChannel())) {
+                        success = true;
+                    }
+                } finally {
+                    fos.close();
+                }
+            } finally {
+                fis.close();
+            }
+        } catch (IOException ioe) {
+            Log.e(LOGTAG, "Exception while attempting to move file to " + dst.getAbsolutePath(), ioe);
+        }
+
+        if (success) {
+            dst.setLastModified(lastModified);
+            src.delete();
+        } else {
+            dst.delete();
+        }
+        return success;
+    }
+
+    private File ensureMozillaDirectory() throws IOException {
+        if (mMozDir != null) {
+            return mMozDir;
+        }
+
+        File filesDir = getFilesDir();
+        File mozDir = new File(filesDir, "mozilla");
+        if (! mozDir.exists()) {
+            if (! mozDir.mkdirs()) {
+                throw new IOException("Unable to create mozilla directory at " + mozDir.getAbsolutePath());
+            }
+        }
+        mMozDir = mozDir;
+        return mMozDir;
+    }
+
+    private File findProfileDir(File mozillaDir) {
+        String suffix = '.' + mName;
+        File[] candidates = mozillaDir.listFiles();
+        if (candidates == null) {
+            return null;
+        }
+        for (File f : candidates) {
+            if (f.isDirectory() && f.getName().endsWith(suffix)) {
+                return f;
+            }
+        }
+        return null;
+    }
+
+    private static String saltProfileName(String name) {
+        String allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789";
+        StringBuffer salt = new StringBuffer(16);
+        for (int i = 0; i < 8; i++) {
+            salt.append(allowedChars.charAt((int)(Math.random() * allowedChars.length())));
+        }
+        salt.append('.');
+        salt.append(name);
+        return salt.toString();
+    }
+
+    private File createProfileDir(File mozillaDir) throws IOException {
+        // XXX: TO-DO If we already have an ini file, we should append the
+        //      new profile information to it. For now we just throw an exception.
+        //      see bug 715391
+        File profileIniFile = new File(mozillaDir, "profiles.ini");
+        if (profileIniFile.exists()) {
+            throw new IOException("Can't create new profiles");
+        }
+
+        String saltedName = saltProfileName(mName);
+        File profileDir = new File(mozillaDir, saltedName);
+        while (profileDir.exists()) {
+            saltedName = saltProfileName(mName);
+            profileDir = new File(mozillaDir, saltedName);
+        }
+
+        if (! profileDir.mkdirs()) {
+            throw new IOException("Unable to create profile at " + profileDir.getAbsolutePath());
+        }
+        Log.d(LOGTAG, "Created new profile dir at " + profileDir.getAbsolutePath());
+
+        FileWriter out = new FileWriter(profileIniFile, true);
+        try {
+            out.write("[General]\n" +
+                      "StartWithLastProfile=1\n" +
+                      "\n" +
+                      "[Profile0]\n" +
+                      "Name=" + mName + "\n" +
+                      "IsRelative=1\n" +
+                      "Path=" + saltedName + "\n" +
+                      "Default=1\n");
+        } finally {
+            out.close();
+        }
+
+        return profileDir;
+    }
+}
--- a/mobile/android/base/LauncherShortcuts.java.in
+++ b/mobile/android/base/LauncherShortcuts.java.in
@@ -142,57 +142,25 @@ public class LauncherShortcuts extends A
     
     private class FetchWebApps extends AsyncTask<Void, Void, Void> {
         
         @Override
         protected Void doInBackground(Void... unused) {
             mWebappsList = null;
             
             Context context = getApplicationContext();
-            
-            File home = new File(context.getFilesDir(), "mozilla");
-            if (!home.exists())
-                home = new File(context.getExternalFilesDir(null).getPath(), "mozilla");
-            
-            if (!home.exists())
-                return null;
-
-            File profile = null;
-            String[] files = home.list();
-            for (String file : files) {
-                if (file.endsWith(".default")) {
-                    profile = new File(home, file);
-                    break;
-                }
+            GeckoProfile profile = GeckoProfile.get(context);
+            String webappsJson = null;
+            try {
+                webappsJson = profile.readFile("webapps" + File.separatorChar + "webapps.json");
+            } catch (IOException ioe) {
+                // unable to load the file, leave webappsJson as null
             }
 
-            if (profile == null)
-                return null;
-
-            // Save the folder path to be used during click event
-            mWebappsFolder = new File(profile, "webapps");
-            if (!mWebappsFolder.exists())
-                return null;
-
-            File webapps = new File(mWebappsFolder, "webapps.json");
-            if (!webapps.exists())
-                return null;
-
-            // Parse the contents into a string
-            String webappsJson = new String();
-            try {
-                BufferedReader in = new BufferedReader(new FileReader(webapps));
-                String line = new String();
-                
-                while ((line = in.readLine()) != null) {
-                    webappsJson += line;
-                }
-            } catch (IOException e) { }
-
-            if (webappsJson.length() == 0)
+            if (TextUtils.isEmpty(webappsJson))
                 return null;
             
             mWebappsList = new ArrayList<HashMap<String, String>>();
 
             try {
                 JSONObject webApps = (JSONObject) new JSONTokener(webappsJson).nextValue();
 
                 @SuppressWarnings("rawtypes") Iterator appKeys = webApps.keys();
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -78,21 +78,21 @@ FENNEC_JAVA_FILES = \
   Favicons.java \
   FloatUtils.java \
   GeckoActionBar.java \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoAsyncTask.java \
   GeckoBatteryManager.java \
   GeckoConnectivityReceiver.java \
-  GeckoDirProvider.java \
   GeckoEvent.java \
   GeckoEventListener.java \
   GeckoInputConnection.java \
   GeckoPreferences.java \
+  GeckoProfile.java \
   GeckoStateListDrawable.java \
   GeckoThread.java \
   GlobalHistory.java \
   LinkPreference.java \
   ProfileMigrator.java \
   PromptService.java \
   sqlite/ByteBufferInputStream.java \
   sqlite/SQLiteBridge.java \
--- a/mobile/android/base/db/BrowserProvider.java.in
+++ b/mobile/android/base/db/BrowserProvider.java.in
@@ -43,17 +43,17 @@ package @ANDROID_PACKAGE_NAME@.db;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
 
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoDirProvider;
+import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Images;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract.URLColumns;
@@ -649,23 +649,17 @@ public class BrowserProvider extends Con
 
         // On Android releases older than 2.3, it's not possible to use
         // SQLiteOpenHelper with a full path. Fallback to using separate
         // db files per profile in the app directory.
         if (Build.VERSION.SDK_INT <= 8) {
             return "browser-" + profile + ".db";
         }
 
-        File profileDir = null;
-        try {
-            profileDir = GeckoDirProvider.getProfileDir(mContext, profile);
-        } catch (IOException ex) {
-            Log.e(LOGTAG, "Error getting profile dir", ex);
-        }
-
+        File profileDir = GeckoProfile.get(mContext, profile).getDir();
         if (profileDir == null) {
             debug("Couldn't find directory for profile: " + profile);
             return null;
         }
 
         String databasePath = new File(profileDir, DATABASE_NAME).getAbsolutePath();
         debug("Successfully created database path for profile: " + databasePath);
 
@@ -749,17 +743,17 @@ public class BrowserProvider extends Con
     @Override
     public boolean onCreate() {
         debug("Creating BrowserProvider");
 
         GeckoAppShell.getHandler().post(new Runnable() {
             public void run() {
                 // Kick this off early. It is synchronized so that other callers will wait
                 try {
-                    GeckoDirProvider.getProfileDir(getContext());
+                    GeckoProfile.get(getContext()).getDir();
                 } catch (Exception ex) {
                     Log.e(LOGTAG, "Error getting profile dir", ex);
                 }
             }
         });
         synchronized (this) {
             mContext = getContext();
             mDatabasePerProfile = new HashMap<String, DatabaseHelper>();
--- a/mobile/android/base/db/PasswordsProvider.java.in
+++ b/mobile/android/base/db/PasswordsProvider.java.in
@@ -9,19 +9,19 @@ import java.io.File;
 import java.io.IOException;
 import java.lang.IllegalArgumentException;
 import java.util.HashMap;
 import java.util.ArrayList;
 import java.util.Random;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoDirProvider;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoEventListener;
+import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.DBUtils;
 import org.mozilla.gecko.db.BrowserContract.Passwords;
 import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sqlite.SQLiteBridge;
 import org.mozilla.gecko.sqlite.SQLiteBridgeException;
@@ -157,23 +157,17 @@ public class PasswordsProvider extends C
         }
 
         Log.d(LOGTAG, "Successfully created database helper for profile: " + profile);
 
         return db;
     }
 
     private String getDatabasePath(String profile) {
-        File profileDir = null;
-        try {
-            profileDir = GeckoDirProvider.getProfileDir(mContext, profile);
-        } catch (IOException ex) {
-            Log.e(LOGTAG, "Error getting profile dir", ex);
-        }
-
+        File profileDir = GeckoProfile.get(mContext, profile).getDir();
         if (profileDir == null) {
             Log.d(LOGTAG, "Couldn't find directory for profile: " + profile);
             return null;
         }
 
         String databasePath = new File(profileDir, DATABASE_NAME).getAbsolutePath();
         return databasePath;
     }