mobile/android/base/GeckoProfile.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/GeckoProfile.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,609 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko;
    1.10 +
    1.11 +import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
    1.12 +import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
    1.13 +import org.mozilla.gecko.Telemetry;
    1.14 +import org.mozilla.gecko.TelemetryContract;
    1.15 +import org.mozilla.gecko.util.INIParser;
    1.16 +import org.mozilla.gecko.util.INISection;
    1.17 +
    1.18 +import android.content.Context;
    1.19 +import android.text.TextUtils;
    1.20 +import android.util.Log;
    1.21 +
    1.22 +import java.io.File;
    1.23 +import java.io.FileOutputStream;
    1.24 +import java.io.FileReader;
    1.25 +import java.io.IOException;
    1.26 +import java.io.OutputStreamWriter;
    1.27 +import java.nio.charset.Charset;
    1.28 +import java.util.Enumeration;
    1.29 +import java.util.HashMap;
    1.30 +import java.util.Hashtable;
    1.31 +
    1.32 +public final class GeckoProfile {
    1.33 +    private static final String LOGTAG = "GeckoProfile";
    1.34 +
    1.35 +    // Used to "lock" the guest profile, so that we'll always restart in it
    1.36 +    private static final String LOCK_FILE_NAME = ".active_lock";
    1.37 +    public static final String DEFAULT_PROFILE = "default";
    1.38 +    private static final String GUEST_PROFILE = "guest";
    1.39 +
    1.40 +    private static HashMap<String, GeckoProfile> sProfileCache = new HashMap<String, GeckoProfile>();
    1.41 +    private static String sDefaultProfileName = null;
    1.42 +
    1.43 +    public static boolean sIsUsingCustomProfile = false;
    1.44 +    private final String mName;
    1.45 +    private final File mMozillaDir;
    1.46 +    private File mProfileDir;             // Not final because this is lazily computed.
    1.47 +
    1.48 +    // Constants to cache whether or not a profile is "locked".
    1.49 +    private enum LockState {
    1.50 +        LOCKED,
    1.51 +        UNLOCKED,
    1.52 +        UNDEFINED
    1.53 +    };
    1.54 +
    1.55 +    // Caches whether or not a profile is "locked". Only used by the guest profile to determine if it should
    1.56 +    // be reused or deleted on startup
    1.57 +    private LockState mLocked = LockState.UNDEFINED;
    1.58 +
    1.59 +    // Caches the guest profile dir.
    1.60 +    private static File sGuestDir = null;
    1.61 +    private static GeckoProfile sGuestProfile = null;
    1.62 +
    1.63 +    private boolean mInGuestMode = false;
    1.64 +
    1.65 +
    1.66 +    public static GeckoProfile get(Context context) {
    1.67 +        boolean isGeckoApp = false;
    1.68 +        try {
    1.69 +            isGeckoApp = context instanceof GeckoApp;
    1.70 +        } catch (NoClassDefFoundError ex) {}
    1.71 +
    1.72 +        if (isGeckoApp) {
    1.73 +            // Check for a cached profile on this context already
    1.74 +            // TODO: We should not be caching profile information on the Activity context
    1.75 +            final GeckoApp geckoApp = (GeckoApp) context;
    1.76 +            if (geckoApp.mProfile != null) {
    1.77 +                return geckoApp.mProfile;
    1.78 +            }
    1.79 +        }
    1.80 +
    1.81 +        // If the guest profile exists and is locked, return it
    1.82 +        GeckoProfile guest = GeckoProfile.getGuestProfile(context);
    1.83 +        if (guest != null && guest.locked()) {
    1.84 +            return guest;
    1.85 +        }
    1.86 +
    1.87 +        if (isGeckoApp) {
    1.88 +            final GeckoApp geckoApp = (GeckoApp) context;
    1.89 +            String defaultProfileName;
    1.90 +            try {
    1.91 +                defaultProfileName = geckoApp.getDefaultProfileName();
    1.92 +            } catch (NoMozillaDirectoryException e) {
    1.93 +                // If this failed, we're screwed. But there are so many callers that
    1.94 +                // we'll just throw a RuntimeException.
    1.95 +                Log.wtf(LOGTAG, "Unable to get default profile name.", e);
    1.96 +                throw new RuntimeException(e);
    1.97 +            }
    1.98 +            // Otherwise, get the default profile for the Activity.
    1.99 +            return get(context, defaultProfileName);
   1.100 +        }
   1.101 +
   1.102 +        return get(context, "");
   1.103 +    }
   1.104 +
   1.105 +    public static GeckoProfile get(Context context, String profileName) {
   1.106 +        synchronized (sProfileCache) {
   1.107 +            GeckoProfile profile = sProfileCache.get(profileName);
   1.108 +            if (profile != null)
   1.109 +                return profile;
   1.110 +        }
   1.111 +        return get(context, profileName, (File)null);
   1.112 +    }
   1.113 +
   1.114 +    public static GeckoProfile get(Context context, String profileName, String profilePath) {
   1.115 +        File dir = null;
   1.116 +        if (!TextUtils.isEmpty(profilePath)) {
   1.117 +            dir = new File(profilePath);
   1.118 +            if (!dir.exists() || !dir.isDirectory()) {
   1.119 +                Log.w(LOGTAG, "requested profile directory missing: " + profilePath);
   1.120 +            }
   1.121 +        }
   1.122 +        return get(context, profileName, dir);
   1.123 +    }
   1.124 +
   1.125 +    public static GeckoProfile get(Context context, String profileName, File profileDir) {
   1.126 +        if (context == null) {
   1.127 +            throw new IllegalArgumentException("context must be non-null");
   1.128 +        }
   1.129 +
   1.130 +        // if no profile was passed in, look for the default profile listed in profiles.ini
   1.131 +        // if that doesn't exist, look for a profile called 'default'
   1.132 +        if (TextUtils.isEmpty(profileName) && profileDir == null) {
   1.133 +            try {
   1.134 +                profileName = GeckoProfile.getDefaultProfileName(context);
   1.135 +            } catch (NoMozillaDirectoryException e) {
   1.136 +                // We're unable to do anything sane here.
   1.137 +                throw new RuntimeException(e);
   1.138 +            }
   1.139 +        }
   1.140 +
   1.141 +        // actually try to look up the profile
   1.142 +        synchronized (sProfileCache) {
   1.143 +            GeckoProfile profile = sProfileCache.get(profileName);
   1.144 +            if (profile == null) {
   1.145 +                try {
   1.146 +                    profile = new GeckoProfile(context, profileName);
   1.147 +                } catch (NoMozillaDirectoryException e) {
   1.148 +                    // We're unable to do anything sane here.
   1.149 +                    throw new RuntimeException(e);
   1.150 +                }
   1.151 +                profile.setDir(profileDir);
   1.152 +                sProfileCache.put(profileName, profile);
   1.153 +            } else {
   1.154 +                profile.setDir(profileDir);
   1.155 +            }
   1.156 +            return profile;
   1.157 +        }
   1.158 +    }
   1.159 +
   1.160 +    public static boolean removeProfile(Context context, String profileName) {
   1.161 +        final boolean success;
   1.162 +        try {
   1.163 +            success = new GeckoProfile(context, profileName).remove();
   1.164 +        } catch (NoMozillaDirectoryException e) {
   1.165 +            Log.w(LOGTAG, "Unable to remove profile: no Mozilla directory.", e);
   1.166 +            return true;
   1.167 +        }
   1.168 +
   1.169 +        if (success) {
   1.170 +            // Clear all shared prefs for the given profile.
   1.171 +            GeckoSharedPrefs.forProfileName(context, profileName)
   1.172 +                            .edit().clear().commit();
   1.173 +        }
   1.174 +
   1.175 +        return success;
   1.176 +    }
   1.177 +
   1.178 +    public static GeckoProfile createGuestProfile(Context context) {
   1.179 +        try {
   1.180 +            removeGuestProfile(context);
   1.181 +            // We need to force the creation of a new guest profile if we want it outside of the normal profile path,
   1.182 +            // otherwise GeckoProfile.getDir will try to be smart and build it for us in the normal profiles dir.
   1.183 +            getGuestDir(context).mkdir();
   1.184 +            GeckoProfile profile = getGuestProfile(context);
   1.185 +            profile.lock();
   1.186 +            return profile;
   1.187 +        } catch (Exception ex) {
   1.188 +            Log.e(LOGTAG, "Error creating guest profile", ex);
   1.189 +        }
   1.190 +        return null;
   1.191 +    }
   1.192 +
   1.193 +    public static void leaveGuestSession(Context context) {
   1.194 +        GeckoProfile profile = getGuestProfile(context);
   1.195 +        if (profile != null) {
   1.196 +            profile.unlock();
   1.197 +        }
   1.198 +    }
   1.199 +
   1.200 +    private static File getGuestDir(Context context) {
   1.201 +        if (sGuestDir == null) {
   1.202 +            sGuestDir = context.getFileStreamPath("guest");
   1.203 +        }
   1.204 +        return sGuestDir;
   1.205 +    }
   1.206 +
   1.207 +    private static GeckoProfile getGuestProfile(Context context) {
   1.208 +        if (sGuestProfile == null) {
   1.209 +            File guestDir = getGuestDir(context);
   1.210 +            if (guestDir.exists()) {
   1.211 +                sGuestProfile = get(context, GUEST_PROFILE, guestDir);
   1.212 +                sGuestProfile.mInGuestMode = true;
   1.213 +            }
   1.214 +        }
   1.215 +
   1.216 +        return sGuestProfile;
   1.217 +    }
   1.218 +
   1.219 +    public static boolean maybeCleanupGuestProfile(final Context context) {
   1.220 +        final GeckoProfile profile = getGuestProfile(context);
   1.221 +
   1.222 +        if (profile == null) {
   1.223 +            return false;
   1.224 +        } else if (!profile.locked()) {
   1.225 +            profile.mInGuestMode = false;
   1.226 +
   1.227 +            // If the guest dir exists, but it's unlocked, delete it
   1.228 +            removeGuestProfile(context);
   1.229 +
   1.230 +            return true;
   1.231 +        }
   1.232 +        return false;
   1.233 +    }
   1.234 +
   1.235 +    private static void removeGuestProfile(Context context) {
   1.236 +        boolean success = false;
   1.237 +        try {
   1.238 +            File guestDir = getGuestDir(context);
   1.239 +            if (guestDir.exists()) {
   1.240 +                success = delete(guestDir);
   1.241 +            }
   1.242 +        } catch (Exception ex) {
   1.243 +            Log.e(LOGTAG, "Error removing guest profile", ex);
   1.244 +        }
   1.245 +
   1.246 +        if (success) {
   1.247 +            // Clear all shared prefs for the guest profile.
   1.248 +            GeckoSharedPrefs.forProfileName(context, GUEST_PROFILE)
   1.249 +                            .edit().clear().commit();
   1.250 +        }
   1.251 +    }
   1.252 +
   1.253 +    public static boolean delete(File file) throws IOException {
   1.254 +        // Try to do a quick initial delete
   1.255 +        if (file.delete())
   1.256 +            return true;
   1.257 +
   1.258 +        if (file.isDirectory()) {
   1.259 +            // If the quick delete failed and this is a dir, recursively delete the contents of the dir
   1.260 +            String files[] = file.list();
   1.261 +            for (String temp : files) {
   1.262 +                File fileDelete = new File(file, temp);
   1.263 +                delete(fileDelete);
   1.264 +            }
   1.265 +        }
   1.266 +
   1.267 +        // Even if this is a dir, it should now be empty and delete should work
   1.268 +        return file.delete();
   1.269 +    }
   1.270 +
   1.271 +    private GeckoProfile(Context context, String profileName) throws NoMozillaDirectoryException {
   1.272 +        mName = profileName;
   1.273 +        mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
   1.274 +    }
   1.275 +
   1.276 +    // Warning, Changing the lock file state from outside apis will cause this to become out of sync
   1.277 +    public boolean locked() {
   1.278 +        if (mLocked != LockState.UNDEFINED) {
   1.279 +            return mLocked == LockState.LOCKED;
   1.280 +        }
   1.281 +
   1.282 +        // Don't use getDir() as it will create a dir if none exists
   1.283 +        if (mProfileDir != null && mProfileDir.exists()) {
   1.284 +            File lockFile = new File(mProfileDir, LOCK_FILE_NAME);
   1.285 +            boolean res = lockFile.exists();
   1.286 +            mLocked = res ? LockState.LOCKED : LockState.UNLOCKED;
   1.287 +        } else {
   1.288 +            mLocked = LockState.UNLOCKED;
   1.289 +        }
   1.290 +
   1.291 +        return mLocked == LockState.LOCKED;
   1.292 +    }
   1.293 +
   1.294 +    public boolean lock() {
   1.295 +        try {
   1.296 +            // If this dir doesn't exist getDir will create it for us
   1.297 +            File lockFile = new File(getDir(), LOCK_FILE_NAME);
   1.298 +            boolean result = lockFile.createNewFile();
   1.299 +            if (result) {
   1.300 +                mLocked = LockState.LOCKED;
   1.301 +            } else {
   1.302 +                mLocked = LockState.UNLOCKED;
   1.303 +            }
   1.304 +            return result;
   1.305 +        } catch(IOException ex) {
   1.306 +            Log.e(LOGTAG, "Error locking profile", ex);
   1.307 +        }
   1.308 +        mLocked = LockState.UNLOCKED;
   1.309 +        return false;
   1.310 +    }
   1.311 +
   1.312 +    public boolean unlock() {
   1.313 +        // Don't use getDir() as it will create a dir
   1.314 +        if (mProfileDir == null || !mProfileDir.exists()) {
   1.315 +            return true;
   1.316 +        }
   1.317 +
   1.318 +        try {
   1.319 +            File lockFile = new File(mProfileDir, LOCK_FILE_NAME);
   1.320 +            boolean result = delete(lockFile);
   1.321 +            if (result) {
   1.322 +                mLocked = LockState.UNLOCKED;
   1.323 +            } else {
   1.324 +                mLocked = LockState.LOCKED;
   1.325 +            }
   1.326 +            return result;
   1.327 +        } catch(IOException ex) {
   1.328 +            Log.e(LOGTAG, "Error unlocking profile", ex);
   1.329 +        }
   1.330 +        mLocked = LockState.LOCKED;
   1.331 +        return false;
   1.332 +    }
   1.333 +
   1.334 +    public boolean inGuestMode() {
   1.335 +        return mInGuestMode;
   1.336 +    }
   1.337 +
   1.338 +    private void setDir(File dir) {
   1.339 +        if (dir != null && dir.exists() && dir.isDirectory()) {
   1.340 +            mProfileDir = dir;
   1.341 +        }
   1.342 +    }
   1.343 +
   1.344 +    public String getName() {
   1.345 +        return mName;
   1.346 +    }
   1.347 +
   1.348 +    public synchronized File getDir() {
   1.349 +        forceCreate();
   1.350 +        return mProfileDir;
   1.351 +    }
   1.352 +
   1.353 +    public synchronized GeckoProfile forceCreate() {
   1.354 +        if (mProfileDir != null) {
   1.355 +            return this;
   1.356 +        }
   1.357 +
   1.358 +        try {
   1.359 +            // Check if a profile with this name already exists.
   1.360 +            try {
   1.361 +                mProfileDir = findProfileDir();
   1.362 +                Log.d(LOGTAG, "Found profile dir.");
   1.363 +            } catch (NoSuchProfileException noSuchProfile) {
   1.364 +                // If it doesn't exist, create it.
   1.365 +                mProfileDir = createProfileDir();
   1.366 +            }
   1.367 +        } catch (IOException ioe) {
   1.368 +            Log.e(LOGTAG, "Error getting profile dir", ioe);
   1.369 +        }
   1.370 +        return this;
   1.371 +    }
   1.372 +
   1.373 +    public File getFile(String aFile) {
   1.374 +        File f = getDir();
   1.375 +        if (f == null)
   1.376 +            return null;
   1.377 +
   1.378 +        return new File(f, aFile);
   1.379 +    }
   1.380 +
   1.381 +    /**
   1.382 +     * Moves the session file to the backup session file.
   1.383 +     *
   1.384 +     * sessionstore.js should hold the current session, and sessionstore.bak
   1.385 +     * should hold the previous session (where it is used to read the "tabs
   1.386 +     * from last time"). Normally, sessionstore.js is moved to sessionstore.bak
   1.387 +     * on a clean quit, but this doesn't happen if Fennec crashed. Thus, this
   1.388 +     * method should be called after a crash so sessionstore.bak correctly
   1.389 +     * holds the previous session.
   1.390 +     */
   1.391 +    public void moveSessionFile() {
   1.392 +        File sessionFile = getFile("sessionstore.js");
   1.393 +        if (sessionFile != null && sessionFile.exists()) {
   1.394 +            File sessionFileBackup = getFile("sessionstore.bak");
   1.395 +            sessionFile.renameTo(sessionFileBackup);
   1.396 +        }
   1.397 +    }
   1.398 +
   1.399 +    /**
   1.400 +     * Get the string from a session file.
   1.401 +     *
   1.402 +     * The session can either be read from sessionstore.js or sessionstore.bak.
   1.403 +     * In general, sessionstore.js holds the current session, and
   1.404 +     * sessionstore.bak holds the previous session.
   1.405 +     *
   1.406 +     * @param readBackup if true, the session is read from sessionstore.bak;
   1.407 +     *                   otherwise, the session is read from sessionstore.js
   1.408 +     *
   1.409 +     * @return the session string
   1.410 +     */
   1.411 +    public String readSessionFile(boolean readBackup) {
   1.412 +        File sessionFile = getFile(readBackup ? "sessionstore.bak" : "sessionstore.js");
   1.413 +
   1.414 +        try {
   1.415 +            if (sessionFile != null && sessionFile.exists()) {
   1.416 +                return readFile(sessionFile);
   1.417 +            }
   1.418 +        } catch (IOException ioe) {
   1.419 +            Log.e(LOGTAG, "Unable to read session file", ioe);
   1.420 +        }
   1.421 +        return null;
   1.422 +    }
   1.423 +
   1.424 +    public String readFile(String filename) throws IOException {
   1.425 +        File dir = getDir();
   1.426 +        if (dir == null) {
   1.427 +            throw new IOException("No profile directory found");
   1.428 +        }
   1.429 +        File target = new File(dir, filename);
   1.430 +        return readFile(target);
   1.431 +    }
   1.432 +
   1.433 +    private String readFile(File target) throws IOException {
   1.434 +        FileReader fr = new FileReader(target);
   1.435 +        try {
   1.436 +            StringBuilder sb = new StringBuilder();
   1.437 +            char[] buf = new char[8192];
   1.438 +            int read = fr.read(buf);
   1.439 +            while (read >= 0) {
   1.440 +                sb.append(buf, 0, read);
   1.441 +                read = fr.read(buf);
   1.442 +            }
   1.443 +            return sb.toString();
   1.444 +        } finally {
   1.445 +            fr.close();
   1.446 +        }
   1.447 +    }
   1.448 +
   1.449 +    private boolean remove() {
   1.450 +        try {
   1.451 +            final File dir = getDir();
   1.452 +            if (dir.exists()) {
   1.453 +                delete(dir);
   1.454 +            }
   1.455 +
   1.456 +            try {
   1.457 +                mProfileDir = findProfileDir();
   1.458 +            } catch (NoSuchProfileException noSuchProfile) {
   1.459 +                // If the profile doesn't exist, there's nothing left for us to do.
   1.460 +                return false;
   1.461 +            }
   1.462 +
   1.463 +            final INIParser parser = GeckoProfileDirectories.getProfilesINI(mMozillaDir);
   1.464 +            final Hashtable<String, INISection> sections = parser.getSections();
   1.465 +            for (Enumeration<INISection> e = sections.elements(); e.hasMoreElements();) {
   1.466 +                final INISection section = e.nextElement();
   1.467 +                String name = section.getStringProperty("Name");
   1.468 +
   1.469 +                if (name == null || !name.equals(mName)) {
   1.470 +                    continue;
   1.471 +                }
   1.472 +
   1.473 +                if (section.getName().startsWith("Profile")) {
   1.474 +                    // ok, we have stupid Profile#-named things.  Rename backwards.
   1.475 +                    try {
   1.476 +                        int sectionNumber = Integer.parseInt(section.getName().substring("Profile".length()));
   1.477 +                        String curSection = "Profile" + sectionNumber;
   1.478 +                        String nextSection = "Profile" + (sectionNumber+1);
   1.479 +
   1.480 +                        sections.remove(curSection);
   1.481 +
   1.482 +                        while (sections.containsKey(nextSection)) {
   1.483 +                            parser.renameSection(nextSection, curSection);
   1.484 +                            sectionNumber++;
   1.485 +
   1.486 +                            curSection = nextSection;
   1.487 +                            nextSection = "Profile" + (sectionNumber+1);
   1.488 +                        }
   1.489 +                    } catch (NumberFormatException nex) {
   1.490 +                        // uhm, malformed Profile thing; we can't do much.
   1.491 +                        Log.e(LOGTAG, "Malformed section name in profiles.ini: " + section.getName());
   1.492 +                        return false;
   1.493 +                    }
   1.494 +                } else {
   1.495 +                    // this really shouldn't be the case, but handle it anyway
   1.496 +                    parser.removeSection(mName);
   1.497 +                }
   1.498 +
   1.499 +                break;
   1.500 +            }
   1.501 +
   1.502 +            parser.write();
   1.503 +            return true;
   1.504 +        } catch (IOException ex) {
   1.505 +            Log.w(LOGTAG, "Failed to remove profile.", ex);
   1.506 +            return false;
   1.507 +        }
   1.508 +    }
   1.509 +
   1.510 +    /**
   1.511 +     * @return the default profile name for this application, or
   1.512 +     *         {@link GeckoProfile#DEFAULT_PROFILE} if none could be found.
   1.513 +     *
   1.514 +     * @throws NoMozillaDirectoryException
   1.515 +     *             if the Mozilla directory did not exist and could not be
   1.516 +     *             created.
   1.517 +     */
   1.518 +    public static String getDefaultProfileName(final Context context) throws NoMozillaDirectoryException {
   1.519 +        // Have we read the default profile from the INI already?
   1.520 +        // Changing the default profile requires a restart, so we don't
   1.521 +        // need to worry about runtime changes.
   1.522 +        if (sDefaultProfileName != null) {
   1.523 +            return sDefaultProfileName;
   1.524 +        }
   1.525 +
   1.526 +        final String profileName = GeckoProfileDirectories.findDefaultProfileName(context);
   1.527 +        if (profileName == null) {
   1.528 +            // Note that we don't persist this back to profiles.ini.
   1.529 +            sDefaultProfileName = DEFAULT_PROFILE;
   1.530 +            return DEFAULT_PROFILE;
   1.531 +        }
   1.532 +
   1.533 +        sDefaultProfileName = profileName;
   1.534 +        return sDefaultProfileName;
   1.535 +    }
   1.536 +
   1.537 +    private File findProfileDir() throws NoSuchProfileException {
   1.538 +        return GeckoProfileDirectories.findProfileDir(mMozillaDir, mName);
   1.539 +    }
   1.540 +
   1.541 +    private File createProfileDir() throws IOException {
   1.542 +        INIParser parser = GeckoProfileDirectories.getProfilesINI(mMozillaDir);
   1.543 +
   1.544 +        // Salt the name of our requested profile
   1.545 +        String saltedName = GeckoProfileDirectories.saltProfileName(mName);
   1.546 +        File profileDir = new File(mMozillaDir, saltedName);
   1.547 +        while (profileDir.exists()) {
   1.548 +            saltedName = GeckoProfileDirectories.saltProfileName(mName);
   1.549 +            profileDir = new File(mMozillaDir, saltedName);
   1.550 +        }
   1.551 +
   1.552 +        // Attempt to create the salted profile dir
   1.553 +        if (!profileDir.mkdirs()) {
   1.554 +            throw new IOException("Unable to create profile.");
   1.555 +        }
   1.556 +        Log.d(LOGTAG, "Created new profile dir.");
   1.557 +
   1.558 +        // Now update profiles.ini
   1.559 +        // If this is the first time its created, we also add a General section
   1.560 +        // look for the first profile number that isn't taken yet
   1.561 +        int profileNum = 0;
   1.562 +        boolean isDefaultSet = false;
   1.563 +        INISection profileSection;
   1.564 +        while ((profileSection = parser.getSection("Profile" + profileNum)) != null) {
   1.565 +            profileNum++;
   1.566 +            if (profileSection.getProperty("Default") != null) {
   1.567 +                isDefaultSet = true;
   1.568 +            }
   1.569 +        }
   1.570 +
   1.571 +        profileSection = new INISection("Profile" + profileNum);
   1.572 +        profileSection.setProperty("Name", mName);
   1.573 +        profileSection.setProperty("IsRelative", 1);
   1.574 +        profileSection.setProperty("Path", saltedName);
   1.575 +
   1.576 +        if (parser.getSection("General") == null) {
   1.577 +            INISection generalSection = new INISection("General");
   1.578 +            generalSection.setProperty("StartWithLastProfile", 1);
   1.579 +            parser.addSection(generalSection);
   1.580 +        }
   1.581 +
   1.582 +        if (!isDefaultSet && !mName.startsWith("webapp")) {
   1.583 +            // only set as default if this is the first non-webapp
   1.584 +            // profile we're creating
   1.585 +            profileSection.setProperty("Default", 1);
   1.586 +
   1.587 +            // We have no intention of stopping this session. The FIRSTRUN session
   1.588 +            // ends when the browsing session/activity has ended. All events
   1.589 +            // during firstrun will be tagged as FIRSTRUN.
   1.590 +            Telemetry.startUISession(TelemetryContract.Session.FIRSTRUN);
   1.591 +        }
   1.592 +
   1.593 +        parser.addSection(profileSection);
   1.594 +        parser.write();
   1.595 +
   1.596 +        // Write out profile creation time, mirroring the logic in nsToolkitProfileService.
   1.597 +        try {
   1.598 +            FileOutputStream stream = new FileOutputStream(profileDir.getAbsolutePath() + File.separator + "times.json");
   1.599 +            OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
   1.600 +            try {
   1.601 +                writer.append("{\"created\": " + System.currentTimeMillis() + "}\n");
   1.602 +            } finally {
   1.603 +                writer.close();
   1.604 +            }
   1.605 +        } catch (Exception e) {
   1.606 +            // Best-effort.
   1.607 +            Log.w(LOGTAG, "Couldn't write times.json.", e);
   1.608 +        }
   1.609 +
   1.610 +        return profileDir;
   1.611 +    }
   1.612 +}

mercurial