summaryrefslogtreecommitdiffstats
path: root/packages/SettingsLib/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SettingsLib/src')
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationCreator.java29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java206
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/animation/DisappearAnimationUtils.java58
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java1362
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java37
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java182
6 files changed, 1817 insertions, 57 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationCreator.java b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationCreator.java
new file mode 100644
index 0000000..8a61e4e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationCreator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.settingslib.animation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An interface which can create animations when starting an appear animation with
+ * {@link AppearAnimationUtils}
+ */
+public interface AppearAnimationCreator<T> {
+ void createAnimation(T animatedObject, long delay, long duration,
+ float translationY, boolean appearing, Interpolator interpolator,
+ Runnable finishListener);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
new file mode 100644
index 0000000..441474d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.settingslib.animation;
+
+import android.content.Context;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.android.settingslib.R;
+
+/**
+ * A class to make nice appear transitions for views in a tabular layout.
+ */
+public class AppearAnimationUtils implements AppearAnimationCreator<View> {
+
+ public static final long DEFAULT_APPEAR_DURATION = 220;
+
+ private final Interpolator mInterpolator;
+ private final float mStartTranslation;
+ private final AppearAnimationProperties mProperties = new AppearAnimationProperties();
+ protected final float mDelayScale;
+ private final long mDuration;
+ protected RowTranslationScaler mRowTranslationScaler;
+ protected boolean mAppearing;
+
+ public AppearAnimationUtils(Context ctx) {
+ this(ctx, DEFAULT_APPEAR_DURATION,
+ 1.0f, 1.0f,
+ AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in));
+ }
+
+ public AppearAnimationUtils(Context ctx, long duration, float translationScaleFactor,
+ float delayScaleFactor, Interpolator interpolator) {
+ mInterpolator = interpolator;
+ mStartTranslation = ctx.getResources().getDimensionPixelOffset(
+ R.dimen.appear_y_translation_start) * translationScaleFactor;
+ mDelayScale = delayScaleFactor;
+ mDuration = duration;
+ mAppearing = true;
+ }
+
+ public void startAnimation2d(View[][] objects, final Runnable finishListener) {
+ startAnimation2d(objects, finishListener, this);
+ }
+
+ public void startAnimation(View[] objects, final Runnable finishListener) {
+ startAnimation(objects, finishListener, this);
+ }
+
+ public <T> void startAnimation2d(T[][] objects, final Runnable finishListener,
+ AppearAnimationCreator<T> creator) {
+ AppearAnimationProperties properties = getDelays(objects);
+ startAnimations(properties, objects, finishListener, creator);
+ }
+
+ public <T> void startAnimation(T[] objects, final Runnable finishListener,
+ AppearAnimationCreator<T> creator) {
+ AppearAnimationProperties properties = getDelays(objects);
+ startAnimations(properties, objects, finishListener, creator);
+ }
+
+ private <T> void startAnimations(AppearAnimationProperties properties, T[] objects,
+ final Runnable finishListener, AppearAnimationCreator<T> creator) {
+ if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) {
+ finishListener.run();
+ return;
+ }
+ for (int row = 0; row < properties.delays.length; row++) {
+ long[] columns = properties.delays[row];
+ long delay = columns[0];
+ Runnable endRunnable = null;
+ if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == 0) {
+ endRunnable = finishListener;
+ }
+ float translationScale = mRowTranslationScaler != null
+ ? mRowTranslationScaler.getRowTranslationScale(row, properties.delays.length)
+ : 1f;
+ float translation = translationScale * mStartTranslation;
+ creator.createAnimation(objects[row], delay, mDuration,
+ mAppearing ? translation : -translation,
+ mAppearing, mInterpolator, endRunnable);
+ }
+ }
+
+ private <T> void startAnimations(AppearAnimationProperties properties, T[][] objects,
+ final Runnable finishListener, AppearAnimationCreator<T> creator) {
+ if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) {
+ finishListener.run();
+ return;
+ }
+ for (int row = 0; row < properties.delays.length; row++) {
+ long[] columns = properties.delays[row];
+ float translationScale = mRowTranslationScaler != null
+ ? mRowTranslationScaler.getRowTranslationScale(row, properties.delays.length)
+ : 1f;
+ float translation = translationScale * mStartTranslation;
+ for (int col = 0; col < columns.length; col++) {
+ long delay = columns[col];
+ Runnable endRunnable = null;
+ if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == col) {
+ endRunnable = finishListener;
+ }
+ creator.createAnimation(objects[row][col], delay, mDuration,
+ mAppearing ? translation : -translation,
+ mAppearing, mInterpolator, endRunnable);
+ }
+ }
+ }
+
+ private <T> AppearAnimationProperties getDelays(T[] items) {
+ long maxDelay = -1;
+ mProperties.maxDelayColIndex = -1;
+ mProperties.maxDelayRowIndex = -1;
+ mProperties.delays = new long[items.length][];
+ for (int row = 0; row < items.length; row++) {
+ mProperties.delays[row] = new long[1];
+ long delay = calculateDelay(row, 0);
+ mProperties.delays[row][0] = delay;
+ if (items[row] != null && delay > maxDelay) {
+ maxDelay = delay;
+ mProperties.maxDelayColIndex = 0;
+ mProperties.maxDelayRowIndex = row;
+ }
+ }
+ return mProperties;
+ }
+
+ private <T> AppearAnimationProperties getDelays(T[][] items) {
+ long maxDelay = -1;
+ mProperties.maxDelayColIndex = -1;
+ mProperties.maxDelayRowIndex = -1;
+ mProperties.delays = new long[items.length][];
+ for (int row = 0; row < items.length; row++) {
+ T[] columns = items[row];
+ mProperties.delays[row] = new long[columns.length];
+ for (int col = 0; col < columns.length; col++) {
+ long delay = calculateDelay(row, col);
+ mProperties.delays[row][col] = delay;
+ if (items[row][col] != null && delay > maxDelay) {
+ maxDelay = delay;
+ mProperties.maxDelayColIndex = col;
+ mProperties.maxDelayRowIndex = row;
+ }
+ }
+ }
+ return mProperties;
+ }
+
+ protected long calculateDelay(int row, int col) {
+ return (long) ((row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20) * mDelayScale);
+ }
+
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ public float getStartTranslation() {
+ return mStartTranslation;
+ }
+
+ @Override
+ public void createAnimation(View view, long delay, long duration, float translationY,
+ boolean appearing, Interpolator interpolator, Runnable endRunnable) {
+ if (view != null) {
+ view.setAlpha(appearing ? 0f : 1.0f);
+ view.setTranslationY(appearing ? translationY : 0);
+ view.animate()
+ .alpha(appearing ? 1f : 0f)
+ .translationY(appearing ? 0 : translationY)
+ .setInterpolator(interpolator)
+ .setDuration(duration)
+ .setStartDelay(delay);
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+ if (endRunnable != null) {
+ view.animate().withEndAction(endRunnable);
+ }
+ }
+ }
+
+ public class AppearAnimationProperties {
+ public long[][] delays;
+ public int maxDelayRowIndex;
+ public int maxDelayColIndex;
+ }
+
+ public interface RowTranslationScaler {
+ float getRowTranslationScale(int row, int numRows);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/animation/DisappearAnimationUtils.java b/packages/SettingsLib/src/com/android/settingslib/animation/DisappearAnimationUtils.java
new file mode 100644
index 0000000..a444ff0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/animation/DisappearAnimationUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.settingslib.animation;
+
+import android.content.Context;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * A class to make nice disappear transitions for views in a tabular layout.
+ */
+public class DisappearAnimationUtils extends AppearAnimationUtils {
+
+ public DisappearAnimationUtils(Context ctx) {
+ this(ctx, DEFAULT_APPEAR_DURATION,
+ 1.0f, 1.0f,
+ AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_linear_in));
+ }
+
+ public DisappearAnimationUtils(Context ctx, long duration, float translationScaleFactor,
+ float delayScaleFactor, Interpolator interpolator) {
+ this(ctx, duration, translationScaleFactor, delayScaleFactor, interpolator,
+ ROW_TRANSLATION_SCALER);
+ }
+
+ public DisappearAnimationUtils(Context ctx, long duration, float translationScaleFactor,
+ float delayScaleFactor, Interpolator interpolator, RowTranslationScaler rowScaler) {
+ super(ctx, duration, translationScaleFactor, delayScaleFactor, interpolator);
+ mRowTranslationScaler = rowScaler;
+ mAppearing = false;
+ }
+
+ protected long calculateDelay(int row, int col) {
+ return (long) ((row * 60 + col * (Math.pow(row, 0.4) + 0.4) * 10) * mDelayScale);
+ }
+
+ private static final RowTranslationScaler ROW_TRANSLATION_SCALER = new RowTranslationScaler() {
+
+ @Override
+ public float getRowTranslationScale(int row, int numRows) {
+ return (float) (Math.pow((numRows - row), 2) / numRows);
+ }
+ };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
new file mode 100644
index 0000000..6561512
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -0,0 +1,1362 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.settingslib.applications;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.format.Formatter;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.text.Collator;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Keeps track of information about all installed applications, lazy-loading
+ * as needed.
+ */
+public class ApplicationsState {
+ static final String TAG = "ApplicationsState";
+ static final boolean DEBUG = false;
+ static final boolean DEBUG_LOCKING = false;
+
+ public static final int SIZE_UNKNOWN = -1;
+ public static final int SIZE_INVALID = -2;
+
+ static final Pattern REMOVE_DIACRITICALS_PATTERN
+ = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+
+ static final Object sLock = new Object();
+ static ApplicationsState sInstance;
+
+ public static ApplicationsState getInstance(Application app) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new ApplicationsState(app);
+ }
+ return sInstance;
+ }
+ }
+
+ final Context mContext;
+ final PackageManager mPm;
+ final IPackageManager mIpm;
+ final UserManager mUm;
+ final int mOwnerRetrieveFlags;
+ final int mRetrieveFlags;
+ PackageIntentReceiver mPackageIntentReceiver;
+
+ boolean mResumed;
+ boolean mHaveDisabledApps;
+
+ // Information about all applications. Synchronize on mEntriesMap
+ // to protect access to these.
+ final ArrayList<Session> mSessions = new ArrayList<Session>();
+ final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
+ final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
+ // Map: userid => (Map: package name => AppEntry)
+ final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
+ new SparseArray<HashMap<String, AppEntry>>();
+ final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
+ List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
+ long mCurId = 1;
+ String mCurComputingSizePkg;
+ int mCurComputingSizeUserId;
+ boolean mSessionsChanged;
+
+ // Temporary for dispatching session callbacks. Only touched by main thread.
+ final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
+
+ final HandlerThread mThread;
+ final BackgroundHandler mBackgroundHandler;
+ final MainHandler mMainHandler = new MainHandler();
+
+ private ApplicationsState(Application app) {
+ mContext = app;
+ mPm = mContext.getPackageManager();
+ mIpm = AppGlobals.getPackageManager();
+ mUm = (UserManager) app.getSystemService(Context.USER_SERVICE);
+ for (UserHandle user : mUm.getUserProfiles()) {
+ mEntriesMap.put(user.getIdentifier(), new HashMap<String, AppEntry>());
+ }
+ mThread = new HandlerThread("ApplicationsState.Loader",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
+
+ // Only the owner can see all apps.
+ mOwnerRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES |
+ PackageManager.GET_DISABLED_COMPONENTS |
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
+ mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS |
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
+
+ /**
+ * This is a trick to prevent the foreground thread from being delayed.
+ * The problem is that Dalvik monitors are initially spin locks, to keep
+ * them lightweight. This leads to unfair contention -- Even though the
+ * background thread only holds the lock for a short amount of time, if
+ * it keeps running and locking again it can prevent the main thread from
+ * acquiring its lock for a long time... sometimes even > 5 seconds
+ * (leading to an ANR).
+ *
+ * Dalvik will promote a monitor to a "real" lock if it detects enough
+ * contention on it. It doesn't figure this out fast enough for us
+ * here, though, so this little trick will force it to turn into a real
+ * lock immediately.
+ */
+ synchronized (mEntriesMap) {
+ try {
+ mEntriesMap.wait(1);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ public Looper getBackgroundLooper() {
+ return mThread.getLooper();
+ }
+
+ public Session newSession(Callbacks callbacks) {
+ Session s = new Session(callbacks);
+ synchronized (mEntriesMap) {
+ mSessions.add(s);
+ }
+ return s;
+ }
+
+ void doResumeIfNeededLocked() {
+ if (mResumed) {
+ return;
+ }
+ mResumed = true;
+ if (mPackageIntentReceiver == null) {
+ mPackageIntentReceiver = new PackageIntentReceiver();
+ mPackageIntentReceiver.registerReceiver();
+ }
+ mApplications = new ArrayList<ApplicationInfo>();
+ for (UserHandle user : mUm.getUserProfiles()) {
+ try {
+ // If this user is new, it needs a map created.
+ if (mEntriesMap.indexOfKey(user.getIdentifier()) < 0) {
+ mEntriesMap.put(user.getIdentifier(), new HashMap<String, AppEntry>());
+ }
+ @SuppressWarnings("unchecked")
+ ParceledListSlice<ApplicationInfo> list =
+ mIpm.getInstalledApplications(
+ user.isOwner() ? mOwnerRetrieveFlags : mRetrieveFlags,
+ user.getIdentifier());
+ mApplications.addAll(list.getList());
+ } catch (RemoteException e) {
+ }
+ }
+
+ if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+ // If an interesting part of the configuration has changed, we
+ // should completely reload the app entries.
+ clearEntries();
+ } else {
+ for (int i=0; i<mAppEntries.size(); i++) {
+ mAppEntries.get(i).sizeStale = true;
+ }
+ }
+
+ mHaveDisabledApps = false;
+ for (int i=0; i<mApplications.size(); i++) {
+ final ApplicationInfo info = mApplications.get(i);
+ // Need to trim out any applications that are disabled by
+ // something different than the user.
+ if (!info.enabled) {
+ if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ mApplications.remove(i);
+ i--;
+ continue;
+ }
+ mHaveDisabledApps = true;
+ }
+ int userId = UserHandle.getUserId(info.uid);
+ final AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
+ if (entry != null) {
+ entry.info = info;
+ }
+ }
+ if (mAppEntries.size() > mApplications.size()) {
+ // There are less apps now, some must have been uninstalled.
+ clearEntries();
+ }
+ mCurComputingSizePkg = null;
+ if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
+ mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
+ }
+ }
+
+ private void clearEntries() {
+ for (int i = 0; i < mEntriesMap.size(); i++) {
+ mEntriesMap.valueAt(i).clear();
+ }
+ mAppEntries.clear();
+ }
+
+ public boolean haveDisabledApps() {
+ return mHaveDisabledApps;
+ }
+
+ void doPauseIfNeededLocked() {
+ if (!mResumed) {
+ return;
+ }
+ for (int i=0; i<mSessions.size(); i++) {
+ if (mSessions.get(i).mResumed) {
+ return;
+ }
+ }
+ doPauseLocked();
+ }
+
+ void doPauseLocked() {
+ mResumed = false;
+ if (mPackageIntentReceiver != null) {
+ mPackageIntentReceiver.unregisterReceiver();
+ mPackageIntentReceiver = null;
+ }
+ }
+
+ public AppEntry getEntry(String packageName, int userId) {
+ if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
+ synchronized (mEntriesMap) {
+ AppEntry entry = mEntriesMap.get(userId).get(packageName);
+ if (entry == null) {
+ ApplicationInfo info = getAppInfoLocked(packageName, userId);
+ if (info == null) {
+ try {
+ info = mIpm.getApplicationInfo(packageName, 0, userId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "getEntry couldn't reach PackageManager", e);
+ return null;
+ }
+ }
+ entry = getEntryLocked(info);
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
+ return entry;
+ }
+ }
+
+ private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
+ for (int i = 0; i < mApplications.size(); i++) {
+ ApplicationInfo info = mApplications.get(i);
+ if (pkg.equals(info.packageName)
+ && userId == UserHandle.getUserId(info.uid)) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public void ensureIcon(AppEntry entry) {
+ if (entry.icon != null) {
+ return;
+ }
+ synchronized (entry) {
+ entry.ensureIconLocked(mContext, mPm);
+ }
+ }
+
+ public void requestSize(String packageName, int userId) {
+ if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
+ synchronized (mEntriesMap) {
+ AppEntry entry = mEntriesMap.get(userId).get(packageName);
+ if (entry != null) {
+ mPm.getPackageSizeInfo(packageName, userId, mBackgroundHandler.mStatsObserver);
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
+ }
+ }
+
+ long sumCacheSizes() {
+ long sum = 0;
+ if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
+ for (int i=mAppEntries.size()-1; i>=0; i--) {
+ sum += mAppEntries.get(i).cacheSize;
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
+ }
+ return sum;
+ }
+
+ int indexOfApplicationInfoLocked(String pkgName, int userId) {
+ for (int i=mApplications.size()-1; i>=0; i--) {
+ ApplicationInfo appInfo = mApplications.get(i);
+ if (appInfo.packageName.equals(pkgName)
+ && UserHandle.getUserId(appInfo.uid) == userId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ void addPackage(String pkgName, int userId) {
+ try {
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
+ if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
+ if (!mResumed) {
+ // If we are not resumed, we will do a full query the
+ // next time we resume, so there is no reason to do work
+ // here.
+ if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
+ return;
+ }
+ if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
+ if (DEBUG) Log.i(TAG, "Package already exists!");
+ if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
+ return;
+ }
+ ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
+ userId == UserHandle.USER_OWNER ? mOwnerRetrieveFlags : mRetrieveFlags,
+ userId);
+ if (info == null) {
+ return;
+ }
+ if (!info.enabled) {
+ if (info.enabledSetting
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ return;
+ }
+ mHaveDisabledApps = true;
+ }
+ mApplications.add(info);
+ if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
+ mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
+ }
+ if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void removePackage(String pkgName, int userId) {
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
+ int idx = indexOfApplicationInfoLocked(pkgName, userId);
+ if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
+ if (idx >= 0) {
+ AppEntry entry = mEntriesMap.get(userId).get(pkgName);
+ if (DEBUG) Log.i(TAG, "removePackage: " + entry);
+ if (entry != null) {
+ mEntriesMap.get(userId).remove(pkgName);
+ mAppEntries.remove(entry);
+ }
+ ApplicationInfo info = mApplications.get(idx);
+ mApplications.remove(idx);
+ if (!info.enabled) {
+ mHaveDisabledApps = false;
+ for (int i=0; i<mApplications.size(); i++) {
+ if (!mApplications.get(i).enabled) {
+ mHaveDisabledApps = true;
+ break;
+ }
+ }
+ }
+ if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
+ }
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
+ }
+ }
+
+ public void invalidatePackage(String pkgName, int userId) {
+ removePackage(pkgName, userId);
+ addPackage(pkgName, userId);
+ }
+
+ private void addUser(int userId) {
+ if (mUm.getUserProfiles().contains(new UserHandle(userId))) {
+ synchronized (mEntriesMap) {
+ mEntriesMap.put(userId, new HashMap<String, AppEntry>());
+ if (mResumed) {
+ // If resumed, Manually pause, then cause a resume to repopulate the app list.
+ // This is the simplest way to reload the packages so that the new user
+ // is included. Otherwise the list will be repopulated on next resume.
+ doPauseLocked();
+ doResumeIfNeededLocked();
+ }
+ if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
+ }
+ }
+ }
+ }
+
+ private void removeUser(int userId) {
+ synchronized (mEntriesMap) {
+ HashMap<String, AppEntry> userMap = mEntriesMap.get(userId);
+ if (userMap != null) {
+ for (AppEntry appEntry : userMap.values()) {
+ mAppEntries.remove(appEntry);
+ mApplications.remove(appEntry.info);
+ }
+ mEntriesMap.remove(userId);
+ if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
+ }
+ }
+ }
+ }
+
+ private AppEntry getEntryLocked(ApplicationInfo info) {
+ int userId = UserHandle.getUserId(info.uid);
+ AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
+ if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
+ if (entry == null) {
+ if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
+ entry = new AppEntry(mContext, info, mCurId++);
+ mEntriesMap.get(userId).put(info.packageName, entry);
+ mAppEntries.add(entry);
+ } else if (entry.info != info) {
+ entry.info = info;
+ }
+ return entry;
+ }
+
+ // --------------------------------------------------------------
+
+ private long getTotalInternalSize(PackageStats ps) {
+ if (ps != null) {
+ return ps.codeSize + ps.dataSize;
+ }
+ return SIZE_INVALID;
+ }
+
+ private long getTotalExternalSize(PackageStats ps) {
+ if (ps != null) {
+ // We also include the cache size here because for non-emulated
+ // we don't automtically clean cache files.
+ return ps.externalCodeSize + ps.externalDataSize
+ + ps.externalCacheSize
+ + ps.externalMediaSize + ps.externalObbSize;
+ }
+ return SIZE_INVALID;
+ }
+
+ private String getSizeStr(long size) {
+ if (size >= 0) {
+ return Formatter.formatFileSize(mContext, size);
+ }
+ return null;
+ }
+
+ void rebuildActiveSessions() {
+ synchronized (mEntriesMap) {
+ if (!mSessionsChanged) {
+ return;
+ }
+ mActiveSessions.clear();
+ for (int i=0; i<mSessions.size(); i++) {
+ Session s = mSessions.get(i);
+ if (s.mResumed) {
+ mActiveSessions.add(s);
+ }
+ }
+ }
+ }
+
+ public static String normalize(String str) {
+ String tmp = Normalizer.normalize(str, Form.NFD);
+ return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
+ .replaceAll("").toLowerCase();
+ }
+
+ public class Session {
+ final Callbacks mCallbacks;
+ boolean mResumed;
+
+ // Rebuilding of app list. Synchronized on mRebuildSync.
+ final Object mRebuildSync = new Object();
+ boolean mRebuildRequested;
+ boolean mRebuildAsync;
+ AppFilter mRebuildFilter;
+ Comparator<AppEntry> mRebuildComparator;
+ ArrayList<AppEntry> mRebuildResult;
+ ArrayList<AppEntry> mLastAppList;
+
+ Session(Callbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ public void resume() {
+ if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
+ synchronized (mEntriesMap) {
+ if (!mResumed) {
+ mResumed = true;
+ mSessionsChanged = true;
+ doResumeIfNeededLocked();
+ }
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
+ }
+
+ public void pause() {
+ if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
+ synchronized (mEntriesMap) {
+ if (mResumed) {
+ mResumed = false;
+ mSessionsChanged = true;
+ mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
+ doPauseIfNeededLocked();
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
+ }
+ }
+
+ public ArrayList<AppEntry> getAllApps() {
+ synchronized (mEntriesMap) {
+ return new ArrayList<>(mAppEntries);
+ }
+ }
+
+ // Creates a new list of app entries with the given filter and comparator.
+ public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
+ synchronized (mRebuildSync) {
+ synchronized (mEntriesMap) {
+ mRebuildingSessions.add(this);
+ mRebuildRequested = true;
+ mRebuildAsync = false;
+ mRebuildFilter = filter;
+ mRebuildComparator = comparator;
+ mRebuildResult = null;
+ if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
+ Message msg = mBackgroundHandler.obtainMessage(
+ BackgroundHandler.MSG_REBUILD_LIST);
+ mBackgroundHandler.sendMessage(msg);
+ }
+ }
+
+ // We will wait for .25s for the list to be built.
+ long waitend = SystemClock.uptimeMillis()+250;
+
+ while (mRebuildResult == null) {
+ long now = SystemClock.uptimeMillis();
+ if (now >= waitend) {
+ break;
+ }
+ try {
+ mRebuildSync.wait(waitend - now);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ mRebuildAsync = true;
+
+ return mRebuildResult;
+ }
+ }
+
+ void handleRebuildList() {
+ AppFilter filter;
+ Comparator<AppEntry> comparator;
+ synchronized (mRebuildSync) {
+ if (!mRebuildRequested) {
+ return;
+ }
+
+ filter = mRebuildFilter;
+ comparator = mRebuildComparator;
+ mRebuildRequested = false;
+ mRebuildFilter = null;
+ mRebuildComparator = null;
+ }
+
+ Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
+
+ if (filter != null) {
+ filter.init();
+ }
+
+ List<AppEntry> apps;
+ synchronized (mEntriesMap) {
+ apps = new ArrayList<>(mAppEntries);
+ }
+
+ ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
+ if (DEBUG) Log.i(TAG, "Rebuilding...");
+ for (int i=0; i<apps.size(); i++) {
+ AppEntry entry = apps.get(i);
+ if (filter == null || filter.filterApp(entry)) {
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
+ entry.ensureLabel(mContext);
+ if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
+ filteredApps.add(entry);
+ if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
+ }
+ }
+ }
+
+ Collections.sort(filteredApps, comparator);
+
+ synchronized (mRebuildSync) {
+ if (!mRebuildRequested) {
+ mLastAppList = filteredApps;
+ if (!mRebuildAsync) {
+ mRebuildResult = filteredApps;
+ mRebuildSync.notifyAll();
+ } else {
+ if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
+ Message msg = mMainHandler.obtainMessage(
+ MainHandler.MSG_REBUILD_COMPLETE, this);
+ mMainHandler.sendMessage(msg);
+ }
+ }
+ }
+ }
+
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ public void release() {
+ pause();
+ synchronized (mEntriesMap) {
+ mSessions.remove(this);
+ }
+ }
+ }
+
+ class MainHandler extends Handler {
+ static final int MSG_REBUILD_COMPLETE = 1;
+ static final int MSG_PACKAGE_LIST_CHANGED = 2;
+ static final int MSG_PACKAGE_ICON_CHANGED = 3;
+ static final int MSG_PACKAGE_SIZE_CHANGED = 4;
+ static final int MSG_ALL_SIZES_COMPUTED = 5;
+ static final int MSG_RUNNING_STATE_CHANGED = 6;
+ static final int MSG_LAUNCHER_INFO_CHANGED = 7;
+ static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
+
+ @Override
+ public void handleMessage(Message msg) {
+ rebuildActiveSessions();
+ switch (msg.what) {
+ case MSG_REBUILD_COMPLETE: {
+ Session s = (Session)msg.obj;
+ if (mActiveSessions.contains(s)) {
+ s.mCallbacks.onRebuildComplete(s.mLastAppList);
+ }
+ } break;
+ case MSG_PACKAGE_LIST_CHANGED: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onPackageListChanged();
+ }
+ } break;
+ case MSG_PACKAGE_ICON_CHANGED: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
+ }
+ } break;
+ case MSG_PACKAGE_SIZE_CHANGED: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
+ (String)msg.obj);
+ }
+ } break;
+ case MSG_ALL_SIZES_COMPUTED: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
+ }
+ } break;
+ case MSG_RUNNING_STATE_CHANGED: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
+ msg.arg1 != 0);
+ }
+ } break;
+ case MSG_LAUNCHER_INFO_CHANGED: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
+ }
+ } break;
+ case MSG_LOAD_ENTRIES_COMPLETE: {
+ for (int i=0; i<mActiveSessions.size(); i++) {
+ mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
+ }
+ } break;
+ }
+ }
+ }
+
+ private class BackgroundHandler extends Handler {
+ static final int MSG_REBUILD_LIST = 1;
+ static final int MSG_LOAD_ENTRIES = 2;
+ static final int MSG_LOAD_ICONS = 3;
+ static final int MSG_LOAD_SIZES = 4;
+ static final int MSG_LOAD_LAUNCHER = 5;
+
+ boolean mRunning;
+
+ BackgroundHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ // Always try rebuilding list first thing, if needed.
+ ArrayList<Session> rebuildingSessions = null;
+ synchronized (mEntriesMap) {
+ if (mRebuildingSessions.size() > 0) {
+ rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
+ mRebuildingSessions.clear();
+ }
+ }
+ if (rebuildingSessions != null) {
+ for (int i=0; i<rebuildingSessions.size(); i++) {
+ rebuildingSessions.get(i).handleRebuildList();
+ }
+ }
+
+ switch (msg.what) {
+ case MSG_REBUILD_LIST: {
+ } break;
+ case MSG_LOAD_ENTRIES: {
+ int numDone = 0;
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
+ for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
+ if (!mRunning) {
+ mRunning = true;
+ Message m = mMainHandler.obtainMessage(
+ MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
+ mMainHandler.sendMessage(m);
+ }
+ ApplicationInfo info = mApplications.get(i);
+ int userId = UserHandle.getUserId(info.uid);
+ if (mEntriesMap.get(userId).get(info.packageName) == null) {
+ numDone++;
+ getEntryLocked(info);
+ }
+ if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
+ // If this app is for a profile and we are on the owner, remove
+ // the owner entry if it isn't installed. This will prevent
+ // duplicates of work only apps showing up as 'not installed
+ // for this user'.
+ // Note: This depends on us traversing the users in order, which
+ // happens because of the way we generate the list in
+ // doResumeIfNeededLocked.
+ AppEntry entry = mEntriesMap.get(0).get(info.packageName);
+ if (entry != null &&
+ (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ mEntriesMap.get(0).remove(info.packageName);
+ mAppEntries.remove(entry);
+ }
+ }
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
+ }
+
+ if (numDone >= 6) {
+ sendEmptyMessage(MSG_LOAD_ENTRIES);
+ } else {
+ if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
+ }
+ sendEmptyMessage(MSG_LOAD_LAUNCHER);
+ }
+ } break;
+ case MSG_LOAD_LAUNCHER: {
+ Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
+ .addCategory(Intent.CATEGORY_LAUNCHER);
+
+ for (int i = 0; i < mEntriesMap.size(); i++) {
+ int userId = mEntriesMap.keyAt(i);
+ List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(launchIntent,
+ PackageManager.GET_DISABLED_COMPONENTS, userId);
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
+ HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
+ final int N = intents.size();
+ for (int j = 0; j < N; j++) {
+ String packageName = intents.get(j).activityInfo.packageName;
+ AppEntry entry = userEntries.get(packageName);
+ if (entry != null) {
+ entry.hasLauncherEntry = true;
+ } else {
+ Log.w(TAG, "Cannot find pkg: " + packageName
+ + " on user " + userId);
+ }
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
+ }
+ }
+
+ if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
+ }
+ sendEmptyMessage(MSG_LOAD_ICONS);
+ } break;
+ case MSG_LOAD_ICONS: {
+ int numDone = 0;
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
+ for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
+ AppEntry entry = mAppEntries.get(i);
+ if (entry.icon == null || !entry.mounted) {
+ synchronized (entry) {
+ if (entry.ensureIconLocked(mContext, mPm)) {
+ if (!mRunning) {
+ mRunning = true;
+ Message m = mMainHandler.obtainMessage(
+ MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
+ mMainHandler.sendMessage(m);
+ }
+ numDone++;
+ }
+ }
+ }
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
+ }
+ if (numDone > 0) {
+ if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
+ }
+ }
+ if (numDone >= 2) {
+ sendEmptyMessage(MSG_LOAD_ICONS);
+ } else {
+ sendEmptyMessage(MSG_LOAD_SIZES);
+ }
+ } break;
+ case MSG_LOAD_SIZES: {
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
+ if (mCurComputingSizePkg != null) {
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
+ return;
+ }
+
+ long now = SystemClock.uptimeMillis();
+ for (int i=0; i<mAppEntries.size(); i++) {
+ AppEntry entry = mAppEntries.get(i);
+ if (entry.size == SIZE_UNKNOWN || entry.sizeStale) {
+ if (entry.sizeLoadStart == 0 ||
+ (entry.sizeLoadStart < (now-20*1000))) {
+ if (!mRunning) {
+ mRunning = true;
+ Message m = mMainHandler.obtainMessage(
+ MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
+ mMainHandler.sendMessage(m);
+ }
+ entry.sizeLoadStart = now;
+ mCurComputingSizePkg = entry.info.packageName;
+ mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid);
+ mPm.getPackageSizeInfo(mCurComputingSizePkg,
+ mCurComputingSizeUserId, mStatsObserver);
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
+ return;
+ }
+ }
+ if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
+ mRunning = false;
+ Message m = mMainHandler.obtainMessage(
+ MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
+ mMainHandler.sendMessage(m);
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
+ }
+ } break;
+ }
+ }
+
+ final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
+ public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
+ boolean sizeChanged = false;
+ synchronized (mEntriesMap) {
+ if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
+ HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
+ if (userMap == null) {
+ // The user must have been removed.
+ return;
+ }
+ AppEntry entry = userMap.get(stats.packageName);
+ if (entry != null) {
+ synchronized (entry) {
+ entry.sizeStale = false;
+ entry.sizeLoadStart = 0;
+ long externalCodeSize = stats.externalCodeSize
+ + stats.externalObbSize;
+ long externalDataSize = stats.externalDataSize
+ + stats.externalMediaSize;
+ long newSize = externalCodeSize + externalDataSize
+ + getTotalInternalSize(stats);
+ if (entry.size != newSize ||
+ entry.cacheSize != stats.cacheSize ||
+ entry.codeSize != stats.codeSize ||
+ entry.dataSize != stats.dataSize ||
+ entry.externalCodeSize != externalCodeSize ||
+ entry.externalDataSize != externalDataSize ||
+ entry.externalCacheSize != stats.externalCacheSize) {
+ entry.size = newSize;
+ entry.cacheSize = stats.cacheSize;
+ entry.codeSize = stats.codeSize;
+ entry.dataSize = stats.dataSize;
+ entry.externalCodeSize = externalCodeSize;
+ entry.externalDataSize = externalDataSize;
+ entry.externalCacheSize = stats.externalCacheSize;
+ entry.sizeStr = getSizeStr(entry.size);
+ entry.internalSize = getTotalInternalSize(stats);
+ entry.internalSizeStr = getSizeStr(entry.internalSize);
+ entry.externalSize = getTotalExternalSize(stats);
+ entry.externalSizeStr = getSizeStr(entry.externalSize);
+ if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
+ + ": " + entry.sizeStr);
+ sizeChanged = true;
+ }
+ }
+ if (sizeChanged) {
+ Message msg = mMainHandler.obtainMessage(
+ MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
+ mMainHandler.sendMessage(msg);
+ }
+ }
+ if (mCurComputingSizePkg != null
+ && (mCurComputingSizePkg.equals(stats.packageName)
+ && mCurComputingSizeUserId == stats.userHandle)) {
+ mCurComputingSizePkg = null;
+ sendEmptyMessage(MSG_LOAD_SIZES);
+ }
+ if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
+ }
+ }
+ };
+ }
+
+ /**
+ * Receives notifications when applications are added/removed.
+ */
+ private class PackageIntentReceiver extends BroadcastReceiver {
+ void registerReceiver() {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(this, filter);
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiver(this, sdFilter);
+ // Register for events related to user creation/deletion.
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_ADDED);
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(this, userFilter);
+ }
+ void unregisterReceiver() {
+ mContext.unregisterReceiver(this);
+ }
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String actionStr = intent.getAction();
+ if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
+ Uri data = intent.getData();
+ String pkgName = data.getEncodedSchemeSpecificPart();
+ for (int i = 0; i < mEntriesMap.size(); i++) {
+ addPackage(pkgName, mEntriesMap.keyAt(i));
+ }
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
+ Uri data = intent.getData();
+ String pkgName = data.getEncodedSchemeSpecificPart();
+ for (int i = 0; i < mEntriesMap.size(); i++) {
+ removePackage(pkgName, mEntriesMap.keyAt(i));
+ }
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
+ Uri data = intent.getData();
+ String pkgName = data.getEncodedSchemeSpecificPart();
+ for (int i = 0; i < mEntriesMap.size(); i++) {
+ invalidatePackage(pkgName, mEntriesMap.keyAt(i));
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
+ Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
+ // When applications become available or unavailable (perhaps because
+ // the SD card was inserted or ejected) we need to refresh the
+ // AppInfo with new label, icon and size information as appropriate
+ // given the newfound (un)availability of the application.
+ // A simple way to do that is to treat the refresh as a package
+ // removal followed by a package addition.
+ String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (pkgList == null || pkgList.length == 0) {
+ // Ignore
+ return;
+ }
+ boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
+ if (avail) {
+ for (String pkgName : pkgList) {
+ for (int i = 0; i < mEntriesMap.size(); i++) {
+ invalidatePackage(pkgName, mEntriesMap.keyAt(i));
+ }
+ }
+ }
+ } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
+ addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
+ } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
+ removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
+ }
+ }
+ }
+
+ public interface Callbacks {
+ void onRunningStateChanged(boolean running);
+ void onPackageListChanged();
+ void onRebuildComplete(ArrayList<AppEntry> apps);
+ void onPackageIconChanged();
+ void onPackageSizeChanged(String packageName);
+ void onAllSizesComputed();
+ void onLauncherInfoChanged();
+ void onLoadEntriesCompleted();
+ }
+
+ public static class SizeInfo {
+ public long cacheSize;
+ public long codeSize;
+ public long dataSize;
+ public long externalCodeSize;
+ public long externalDataSize;
+
+ // This is the part of externalDataSize that is in the cache
+ // section of external storage. Note that we don't just combine
+ // this with cacheSize because currently the platform can't
+ // automatically trim this data when needed, so it is something
+ // the user may need to manage. The externalDataSize also includes
+ // this value, since what this is here is really the part of
+ // externalDataSize that we can just consider to be "cache" files
+ // for purposes of cleaning them up in the app details UI.
+ public long externalCacheSize;
+ }
+
+ public static class AppEntry extends SizeInfo {
+ public final File apkFile;
+ public final long id;
+ public String label;
+ public long size;
+ public long internalSize;
+ public long externalSize;
+
+ public boolean mounted;
+
+ public boolean hasLauncherEntry;
+
+ public String getNormalizedLabel() {
+ if (normalizedLabel != null) {
+ return normalizedLabel;
+ }
+ normalizedLabel = normalize(label);
+ return normalizedLabel;
+ }
+
+ // Need to synchronize on 'this' for the following.
+ public ApplicationInfo info;
+ public Drawable icon;
+ public String sizeStr;
+ public String internalSizeStr;
+ public String externalSizeStr;
+ public boolean sizeStale;
+ public long sizeLoadStart;
+
+ public String normalizedLabel;
+
+ // A location where extra info can be placed to be used by custom filters.
+ public Object extraInfo;
+
+ AppEntry(Context context, ApplicationInfo info, long id) {
+ apkFile = new File(info.sourceDir);
+ this.id = id;
+ this.info = info;
+ this.size = SIZE_UNKNOWN;
+ this.sizeStale = true;
+ ensureLabel(context);
+ }
+
+ public void ensureLabel(Context context) {
+ if (this.label == null || !this.mounted) {
+ if (!this.apkFile.exists()) {
+ this.mounted = false;
+ this.label = info.packageName;
+ } else {
+ this.mounted = true;
+ CharSequence label = info.loadLabel(context.getPackageManager());
+ this.label = label != null ? label.toString() : info.packageName;
+ }
+ }
+ }
+
+ boolean ensureIconLocked(Context context, PackageManager pm) {
+ if (this.icon == null) {
+ if (this.apkFile.exists()) {
+ this.icon = getBadgedIcon(pm);
+ return true;
+ } else {
+ this.mounted = false;
+ this.icon = context.getDrawable(
+ com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
+ }
+ } else if (!this.mounted) {
+ // If the app wasn't mounted but is now mounted, reload
+ // its icon.
+ if (this.apkFile.exists()) {
+ this.mounted = true;
+ this.icon = getBadgedIcon(pm);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Drawable getBadgedIcon(PackageManager pm) {
+ // Do badging ourself so that it comes from the user of the app not the current user.
+ return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info),
+ new UserHandle(UserHandle.getUserId(info.uid)));
+ }
+
+ public String getVersion(Context context) {
+ try {
+ return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ return "";
+ }
+ }
+ }
+
+ public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppEntry object1, AppEntry object2) {
+ return sCollator.compare(object1.label, object2.label);
+ }
+ };
+
+ public static final Comparator<AppEntry> SIZE_COMPARATOR
+ = new Comparator<AppEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppEntry object1, AppEntry object2) {
+ if (object1.size < object2.size) return 1;
+ if (object1.size > object2.size) return -1;
+ return sCollator.compare(object1.label, object2.label);
+ }
+ };
+
+ public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
+ = new Comparator<AppEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppEntry object1, AppEntry object2) {
+ if (object1.internalSize < object2.internalSize) return 1;
+ if (object1.internalSize > object2.internalSize) return -1;
+ return sCollator.compare(object1.label, object2.label);
+ }
+ };
+
+ public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
+ = new Comparator<AppEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppEntry object1, AppEntry object2) {
+ if (object1.externalSize < object2.externalSize) return 1;
+ if (object1.externalSize > object2.externalSize) return -1;
+ return sCollator.compare(object1.label, object2.label);
+ }
+ };
+
+ public interface AppFilter {
+ void init();
+ boolean filterApp(AppEntry info);
+ }
+
+ public static final AppFilter FILTER_PERSONAL = new AppFilter() {
+ private int mCurrentUser;
+
+ public void init() {
+ mCurrentUser = ActivityManager.getCurrentUser();
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
+ }
+ };
+
+ public static final AppFilter FILTER_WORK = new AppFilter() {
+ private int mCurrentUser;
+
+ public void init() {
+ mCurrentUser = ActivityManager.getCurrentUser();
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
+ }
+ };
+
+ public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ return true;
+ } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return true;
+ } else if (entry.hasLauncherEntry) {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ return true;
+ } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ public static final AppFilter FILTER_DISABLED = new AppFilter() {
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return !entry.info.enabled;
+ }
+ };
+
+ public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return entry.info.enabled;
+ }
+ };
+
+ public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return true;
+ }
+ };
+
+ public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ return (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
+ }
+ };
+
+ public static class VolumeFilter implements AppFilter {
+ private final String mVolumeUuid;
+
+ public VolumeFilter(String volumeUuid) {
+ mVolumeUuid = volumeUuid;
+ }
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return Objects.equals(info.info.volumeUuid, mVolumeUuid);
+ }
+ }
+
+ public static class CompoundFilter implements AppFilter {
+ private final AppFilter mFirstFilter;
+ private final AppFilter mSecondFilter;
+
+ public CompoundFilter(AppFilter first, AppFilter second) {
+ mFirstFilter = first;
+ mSecondFilter = second;
+ }
+
+ @Override
+ public void init() {
+ mFirstFilter.init();
+ mSecondFilter.init();
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
new file mode 100644
index 0000000..d34dd89
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.settingslib.applications;
+
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+public class InterestingConfigChanges {
+ private final Configuration mLastConfiguration = new Configuration();
+ private int mLastDensity;
+
+ public boolean applyNewConfig(Resources res) {
+ int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+ boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
+ if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
+ |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
+ mLastDensity = res.getDisplayMetrics().densityDpi;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 12ead4e..7b5bfb5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -32,13 +32,14 @@ import org.xmlpull.v1.XmlPullParserException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import java.util.TimeZone;
+import java.util.TreeSet;
public class ZoneGetter {
private static final String TAG = "ZoneGetter";
@@ -50,17 +51,120 @@ public class ZoneGetter {
public static final String KEY_GMT = "gmt"; // value: String
public static final String KEY_OFFSET = "offset"; // value: int (Integer)
- private final List<HashMap<String, Object>> mZones = new ArrayList<>();
- private final HashSet<String> mLocalZones = new HashSet<>();
- private final Date mNow = Calendar.getInstance().getTime();
- private final SimpleDateFormat mZoneNameFormatter = new SimpleDateFormat("zzzz");
+ private ZoneGetter() {}
- public List<HashMap<String, Object>> getZones(Context context) {
- for (String olsonId : TimeZoneNames.forLocale(Locale.getDefault())) {
- mLocalZones.add(olsonId);
+ public static String getTimeZoneOffsetAndName(TimeZone tz, Date now) {
+ Locale locale = Locale.getDefault();
+ String gmtString = getGmtOffsetString(locale, tz, now);
+ String zoneNameString = getZoneLongName(locale, tz, now);
+ if (zoneNameString == null) {
+ return gmtString;
+ }
+
+ // We don't use punctuation here to avoid having to worry about localizing that too!
+ return gmtString + " " + zoneNameString;
+ }
+
+ public static List<Map<String, Object>> getZonesList(Context context) {
+ final Locale locale = Locale.getDefault();
+ final Date now = new Date();
+
+ // The display name chosen for each zone entry depends on whether the zone is one associated
+ // with the country of the user's chosen locale. For "local" zones we prefer the "long name"
+ // (e.g. "Europe/London" -> "British Summer Time" for people in the UK). For "non-local"
+ // zones we prefer the exemplar location (e.g. "Europe/London" -> "London" for English
+ // speakers from outside the UK). This heuristic is based on the fact that people are
+ // typically familiar with their local timezones and exemplar locations don't always match
+ // modern-day expectations for people living in the country covered. Large countries like
+ // China that mostly use a single timezone (olson id: "Asia/Shanghai") may not live near
+ // "Shanghai" and prefer the long name over the exemplar location. The only time we don't
+ // follow this policy for local zones is when Android supplies multiple olson IDs to choose
+ // from and the use of a zone's long name leads to ambiguity. For example, at the time of
+ // writing Android lists 5 olson ids for Australia which collapse to 2 different zone names
+ // in winter but 4 different zone names in summer. The ambiguity leads to the users
+ // selecting the wrong olson ids.
+
+ // Get the list of olson ids to display to the user.
+ List<String> olsonIdsToDisplay = readTimezonesToDisplay(context);
+
+ // Create a lookup of local zone IDs.
+ Set<String> localZoneIds = new TreeSet<String>();
+ for (String olsonId : TimeZoneNames.forLocale(locale)) {
+ localZoneIds.add(olsonId);
+ }
+
+ // Work out whether the long names for the local entries that we would show by default would
+ // be ambiguous.
+ Set<String> localZoneNames = new TreeSet<String>();
+ boolean localLongNamesAreAmbiguous = false;
+ for (String olsonId : olsonIdsToDisplay) {
+ if (localZoneIds.contains(olsonId)) {
+ TimeZone tz = TimeZone.getTimeZone(olsonId);
+ String zoneLongName = getZoneLongName(locale, tz, now);
+ boolean longNameIsUnique = localZoneNames.add(zoneLongName);
+ if (!longNameIsUnique) {
+ localLongNamesAreAmbiguous = true;
+ break;
+ }
+ }
+ }
+
+ // Generate the list of zone entries to return.
+ List<Map<String, Object>> zones = new ArrayList<Map<String, Object>>();
+ for (String olsonId : olsonIdsToDisplay) {
+ final TimeZone tz = TimeZone.getTimeZone(olsonId);
+ // Exemplar location display is the default. The only time we intend to display the long
+ // name is when the olsonId is local AND long names are not ambiguous.
+ boolean isLocalZoneId = localZoneIds.contains(olsonId);
+ boolean preferLongName = isLocalZoneId && !localLongNamesAreAmbiguous;
+ String displayName = getZoneDisplayName(locale, tz, now, preferLongName);
+
+ String gmtOffsetString = getGmtOffsetString(locale, tz, now);
+ int offsetMillis = tz.getOffset(now.getTime());
+ Map<String, Object> displayEntry =
+ createDisplayEntry(tz, gmtOffsetString, displayName, offsetMillis);
+ zones.add(displayEntry);
+ }
+ return zones;
+ }
+
+ private static Map<String, Object> createDisplayEntry(
+ TimeZone tz, String gmtOffsetString, String displayName, int offsetMillis) {
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put(KEY_ID, tz.getID());
+ map.put(KEY_DISPLAYNAME, displayName);
+ map.put(KEY_GMT, gmtOffsetString);
+ map.put(KEY_OFFSET, offsetMillis);
+ return map;
+ }
+
+ /**
+ * Returns a name for the specific zone. If {@code preferLongName} is {@code true} then the
+ * long display name for the timezone will be used, otherwise the exemplar location will be
+ * preferred.
+ */
+ private static String getZoneDisplayName(Locale locale, TimeZone tz, Date now,
+ boolean preferLongName) {
+ String zoneNameString;
+ if (preferLongName) {
+ zoneNameString = getZoneLongName(locale, tz, now);
+ } else {
+ zoneNameString = getZoneExemplarLocation(locale, tz);
+ if (zoneNameString == null || zoneNameString.isEmpty()) {
+ // getZoneExemplarLocation can return null.
+ zoneNameString = getZoneLongName(locale, tz, now);
+ }
}
- try {
- XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones);
+ return zoneNameString;
+ }
+
+ private static String getZoneExemplarLocation(Locale locale, TimeZone tz) {
+ return TimeZoneNames.getExemplarLocation(locale.toString(), tz.getID());
+ }
+
+ private static List<String> readTimezonesToDisplay(Context context) {
+ List<String> olsonIds = new ArrayList<String>();
+ try (XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones)) {
while (xrp.next() != XmlResourceParser.START_TAG) {
continue;
}
@@ -68,58 +172,34 @@ public class ZoneGetter {
while (xrp.getEventType() != XmlResourceParser.END_TAG) {
while (xrp.getEventType() != XmlResourceParser.START_TAG) {
if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) {
- return mZones;
+ return olsonIds;
}
xrp.next();
}
if (xrp.getName().equals(XMLTAG_TIMEZONE)) {
String olsonId = xrp.getAttributeValue(0);
- addTimeZone(olsonId);
+ olsonIds.add(olsonId);
}
while (xrp.getEventType() != XmlResourceParser.END_TAG) {
xrp.next();
}
xrp.next();
}
- xrp.close();
} catch (XmlPullParserException xppe) {
Log.e(TAG, "Ill-formatted timezones.xml file");
} catch (java.io.IOException ioe) {
Log.e(TAG, "Unable to read timezones.xml file");
}
- return mZones;
+ return olsonIds;
}
- private void addTimeZone(String olsonId) {
- // We always need the "GMT-07:00" string.
- final TimeZone tz = TimeZone.getTimeZone(olsonId);
-
- // For the display name, we treat time zones within the country differently
- // from other countries' time zones. So in en_US you'd get "Pacific Daylight Time"
- // but in de_DE you'd get "Los Angeles" for the same time zone.
- String displayName;
- if (mLocalZones.contains(olsonId)) {
- // Within a country, we just use the local name for the time zone.
- mZoneNameFormatter.setTimeZone(tz);
- displayName = mZoneNameFormatter.format(mNow);
- } else {
- // For other countries' time zones, we use the exemplar location.
- final String localeName = Locale.getDefault().toString();
- displayName = TimeZoneNames.getExemplarLocation(localeName, olsonId);
- }
-
- final HashMap<String, Object> map = new HashMap<>();
- map.put(KEY_ID, olsonId);
- map.put(KEY_DISPLAYNAME, displayName);
- map.put(KEY_GMT, getTimeZoneText(tz, false));
- map.put(KEY_OFFSET, tz.getOffset(mNow.getTime()));
-
- mZones.add(map);
+ private static String getZoneLongName(Locale locale, TimeZone tz, Date now) {
+ boolean daylight = tz.inDaylightTime(now);
+ // This returns a name if it can, or will fall back to GMT+0X:00 format.
+ return tz.getDisplayName(daylight, TimeZone.LONG, locale);
}
- public static String getTimeZoneText(TimeZone tz, boolean includeName) {
- Date now = new Date();
-
+ private static String getGmtOffsetString(Locale locale, TimeZone tz, Date now) {
// Use SimpleDateFormat to format the GMT+00:00 string.
SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ");
gmtFormatter.setTimeZone(tz);
@@ -127,21 +207,9 @@ public class ZoneGetter {
// Ensure that the "GMT+" stays with the "00:00" even if the digits are RTL.
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- Locale l = Locale.getDefault();
- boolean isRtl = TextUtils.getLayoutDirectionFromLocale(l) == View.LAYOUT_DIRECTION_RTL;
+ boolean isRtl = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
gmtString = bidiFormatter.unicodeWrap(gmtString,
isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR);
-
- if (!includeName) {
- return gmtString;
- }
-
- // Optionally append the time zone name.
- SimpleDateFormat zoneNameFormatter = new SimpleDateFormat("zzzz");
- zoneNameFormatter.setTimeZone(tz);
- String zoneNameString = zoneNameFormatter.format(now);
-
- // We don't use punctuation here to avoid having to worry about localizing that too!
- return gmtString + " " + zoneNameString;
+ return gmtString;
}
}