Merge "Fix 3106227: use WeakReferences for receivers in DigitalClock class" into gingerbread
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index fa47ff6..303a1bf 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Typeface;
 import android.os.Handler;
@@ -33,6 +32,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import java.lang.ref.WeakReference;
 import java.text.DateFormatSymbols;
 import java.util.Calendar;
 
@@ -49,26 +49,41 @@
     private TextView mTimeDisplay;
     private AmPm mAmPm;
     private ContentObserver mFormatChangeObserver;
-    private boolean mLive = true;
-    private boolean mAttached;
+    private int mAttached = 0; // for debugging - tells us whether attach/detach is unbalanced
 
     /* called by system on minute ticks */
     private final Handler mHandler = new Handler();
-    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (mLive && intent.getAction().equals(
-                            Intent.ACTION_TIMEZONE_CHANGED)) {
-                    mCalendar = Calendar.getInstance();
-                }
-                // Post a runnable to avoid blocking the broadcast.
-                mHandler.post(new Runnable() {
-                        public void run() {
-                            updateTime();
+    private BroadcastReceiver mIntentReceiver;
+
+    private static class TimeChangedReceiver extends BroadcastReceiver {
+        private WeakReference<DigitalClock> mClock;
+        private Context mContext;
+
+        public TimeChangedReceiver(DigitalClock clock) {
+            mClock = new WeakReference<DigitalClock>(clock);
+            mContext = clock.getContext();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Post a runnable to avoid blocking the broadcast.
+            final boolean timezoneChanged =
+                    intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED);
+            final DigitalClock clock = mClock.get();
+            if (clock != null) {
+                clock.mHandler.post(new Runnable() {
+                    public void run() {
+                        if (timezoneChanged) {
+                            clock.mCalendar = Calendar.getInstance();
                         }
+                        clock.updateTime();
+                    }
                 });
+            } else {
+                mContext.unregisterReceiver(this);
             }
-        };
+        }
+    };
 
     static class AmPm {
         private TextView mAmPm;
@@ -94,14 +109,23 @@
         }
     }
 
-    private class FormatChangeObserver extends ContentObserver {
-        public FormatChangeObserver() {
+    private static class FormatChangeObserver extends ContentObserver {
+        private WeakReference<DigitalClock> mClock;
+        private Context mContext;
+        public FormatChangeObserver(DigitalClock clock) {
             super(new Handler());
+            mClock = new WeakReference<DigitalClock>(clock);
+            mContext = clock.getContext();
         }
         @Override
         public void onChange(boolean selfChange) {
-            setDateFormat();
-            updateTime();
+            DigitalClock digitalClock = mClock.get();
+            if (digitalClock != null) {
+                digitalClock.setDateFormat();
+                digitalClock.updateTime();
+            } else {
+                mContext.getContentResolver().unregisterContentObserver(this);
+            }
         }
     }
 
@@ -129,11 +153,11 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        if (mAttached) return;
-        mAttached = true;
+        mAttached++;
 
-        if (mLive) {
-            /* monitor time ticks, time changed, timezone */
+        /* monitor time ticks, time changed, timezone */
+        if (mIntentReceiver == null) {
+            mIntentReceiver = new TimeChangedReceiver(this);
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_TIME_TICK);
             filter.addAction(Intent.ACTION_TIME_CHANGED);
@@ -142,9 +166,11 @@
         }
 
         /* monitor 12/24-hour display preference */
-        mFormatChangeObserver = new FormatChangeObserver();
-        mContext.getContentResolver().registerContentObserver(
-                Settings.System.CONTENT_URI, true, mFormatChangeObserver);
+        if (mFormatChangeObserver == null) {
+            mFormatChangeObserver = new FormatChangeObserver(this);
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.System.CONTENT_URI, true, mFormatChangeObserver);
+        }
 
         updateTime();
     }
@@ -153,16 +179,19 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        if (!mAttached) return;
-        mAttached = false;
+        mAttached--;
 
-        if (mLive) {
+        if (mIntentReceiver != null) {
             mContext.unregisterReceiver(mIntentReceiver);
         }
-        mContext.getContentResolver().unregisterContentObserver(
-                mFormatChangeObserver);
-    }
+        if (mFormatChangeObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(
+                    mFormatChangeObserver);
+        }
 
+        mFormatChangeObserver = null;
+        mIntentReceiver = null;
+    }
 
     void updateTime(Calendar c) {
         mCalendar = c;
@@ -170,9 +199,7 @@
     }
 
     private void updateTime() {
-        if (mLive) {
-            mCalendar.setTimeInMillis(System.currentTimeMillis());
-        }
+        mCalendar.setTimeInMillis(System.currentTimeMillis());
 
         CharSequence newTime = DateFormat.format(mFormat, mCalendar);
         mTimeDisplay.setText(newTime);
@@ -180,12 +207,8 @@
     }
 
     private void setDateFormat() {
-        mFormat = android.text.format.DateFormat.is24HourFormat(getContext()) 
+        mFormat = android.text.format.DateFormat.is24HourFormat(getContext())
             ? M24 : M12;
         mAmPm.setShowAmPm(mFormat.equals(M12));
     }
-
-    void setLive(boolean live) {
-        mLive = live;
-    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
index 840c5e1..c4feefd 100644
--- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
@@ -62,8 +62,8 @@
      */
     private static final int AWAKE_POKE_MILLIS = 30000;
 
-    private final KeyguardScreenCallback mCallback;
-    private final LockPatternUtils mLockPatternUtils;
+    private KeyguardScreenCallback mCallback;
+    private LockPatternUtils mLockPatternUtils;
     private KeyguardUpdateMonitor mUpdateMonitor;
 
     private TextView mTopHeader;
@@ -159,7 +159,10 @@
         if (mCheckingDialog != null) {
             mCheckingDialog.hide();
         }
-        mUpdateMonitor.removeCallback(this);
+        mUpdateMonitor.removeCallback(this); // this must be first
+        mCallback = null;
+        mLockPatternUtils = null;
+        mUpdateMonitor = null;
     }
 
     /** {@inheritDoc} */
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
index ba1d7f5..708e89d 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
@@ -223,8 +223,8 @@
                 mKeyguardHost.postDelayed(new Runnable() {
                     public void run() {
                         synchronized (KeyguardViewManager.this) {
-                            mKeyguardHost.removeView(lastView);
                             lastView.cleanUp();
+                            mKeyguardHost.removeView(lastView);
                         }
                     }
                 }, 500);
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 27706ef..f3d07ab 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -495,8 +495,10 @@
     public void cleanUp() {
         ((KeyguardScreen) mLockScreen).onPause();
         ((KeyguardScreen) mLockScreen).cleanUp();
+        this.removeView(mLockScreen);
         ((KeyguardScreen) mUnlockScreen).onPause();
         ((KeyguardScreen) mUnlockScreen).cleanUp();
+        this.removeView(mUnlockScreen);
     }
 
     private boolean isSecure() {
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index 635b7d3..5a20b93 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -55,9 +55,9 @@
 
     private Status mStatus = Status.Normal;
 
-    private final LockPatternUtils mLockPatternUtils;
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final KeyguardScreenCallback mCallback;
+    private LockPatternUtils mLockPatternUtils;
+    private KeyguardUpdateMonitor mUpdateMonitor;
+    private KeyguardScreenCallback mCallback;
 
     private TextView mCarrier;
     private SlidingTab mSelector;
@@ -225,8 +225,8 @@
         setFocusableInTouchMode(true);
         setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
 
-        updateMonitor.registerInfoCallback(this);
-        updateMonitor.registerSimStateCallback(this);
+        mUpdateMonitor.registerInfoCallback(this);
+        mUpdateMonitor.registerSimStateCallback(this);
 
         mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
         mSilentMode = isSilentMode();
@@ -668,7 +668,10 @@
 
     /** {@inheritDoc} */
     public void cleanUp() {
-        mUpdateMonitor.removeCallback(this);
+        mUpdateMonitor.removeCallback(this); // this must be first
+        mLockPatternUtils = null;
+        mUpdateMonitor = null;
+        mCallback = null;
     }
 
     /** {@inheritDoc} */
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
index 418e243..ffd0a47 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
@@ -66,9 +66,9 @@
     private int mTotalFailedPatternAttempts = 0;
     private CountDownTimer mCountdownTimer = null;
 
-    private final LockPatternUtils mLockPatternUtils;
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final KeyguardScreenCallback mCallback;
+    private LockPatternUtils mLockPatternUtils;
+    private KeyguardUpdateMonitor mUpdateMonitor;
+    private KeyguardScreenCallback mCallback;
 
     /**
      * whether there is a fallback option available when the pattern is forgotten.
@@ -478,6 +478,9 @@
     /** {@inheritDoc} */
     public void cleanUp() {
         mUpdateMonitor.removeCallback(this);
+        mLockPatternUtils = null;
+        mUpdateMonitor = null;
+        mCallback = null;
     }
 
     @Override