Build a new FooterPreference UI.

- Create a new layout for footer prefs.
- Create a new FooterPreference type to use the layout
- Create a Mixin to create and add the pref to screen
- Create a new lifecycle observer type to invoke mixin at right time
- Switch SettingsPreferenceFragment to use footer mixin.
- Switch FingerprintSettings to use the new footer pref.

Bug: 33579394
Test: RunSettingsRoboTests
Change-Id: I548ac39a0d120196a7ffed09b4f98bd9a80bae90
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index 5f42579..ee500a9 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -48,6 +48,7 @@
 import com.android.settings.core.InstrumentedPreferenceFragment;
 import com.android.settings.core.instrumentation.Instrumentable;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.widget.FooterPreferenceMixin;
 import com.android.settingslib.HelpUtils;
 
 import java.util.UUID;
@@ -70,6 +71,9 @@
 
     private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
 
+    protected final FooterPreferenceMixin mFooterPreferenceMixin =
+            new FooterPreferenceMixin(this, getLifecycle());
+
     private SettingsDialogFragment mDialogFragment;
 
     private String mHelpUri;
@@ -123,7 +127,6 @@
 
     private LayoutPreference mHeader;
 
-    private LayoutPreference mFooter;
     private View mEmptyView;
     private LinearLayoutManager mLayoutManager;
     private HighlightablePreferenceGroupAdapter mAdapter;
@@ -277,10 +280,6 @@
         return mHeader;
     }
 
-    public LayoutPreference getFooterView() {
-        return mFooter;
-    }
-
     protected void setHeaderView(int resource) {
         mHeader = new LayoutPreference(getPrefContext(), resource);
         addPreferenceToTop(mHeader);
@@ -298,29 +297,6 @@
         }
     }
 
-    protected void setFooterView(int resource) {
-        setFooterView(resource != 0 ? new LayoutPreference(getPrefContext(), resource) : null);
-    }
-
-    protected void setFooterView(View v) {
-        setFooterView(v != null ? new LayoutPreference(getPrefContext(), v) : null);
-    }
-
-    private void setFooterView(LayoutPreference footer) {
-        if (getPreferenceScreen() != null && mFooter != null) {
-            getPreferenceScreen().removePreference(mFooter);
-        }
-        if (footer != null) {
-            mFooter = footer;
-            mFooter.setOrder(ORDER_LAST);
-            if (getPreferenceScreen() != null) {
-                getPreferenceScreen().addPreference(mFooter);
-            }
-        } else {
-            mFooter = null;
-        }
-    }
-
     @Override
     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
         if (preferenceScreen != null && !preferenceScreen.isAttached()) {
@@ -332,9 +308,6 @@
             if (mHeader != null) {
                 preferenceScreen.addPreference(mHeader);
             }
-            if (mFooter != null) {
-                preferenceScreen.addPreference(mFooter);
-            }
         }
     }
 
@@ -343,7 +316,7 @@
         if (getPreferenceScreen() != null) {
             boolean show = (getPreferenceScreen().getPreferenceCount()
                     - (mHeader != null ? 1 : 0)
-                    - (mFooter != null ? 1 : 0)) <= 0;
+                    - (mFooterPreferenceMixin.hasFooter() ? 1 : 0)) <= 0;
             mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE);
         } else {
             mEmptyView.setVisibility(View.VISIBLE);
diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java
index 8c7b257..bb0fd4c 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProvider.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java
@@ -35,7 +35,7 @@
     /**
      * Callback that receives the total number of packages installed on the device.
      */
-    public interface NumberOfInstalledAppsCallback {
+    interface NumberOfInstalledAppsCallback {
         void onNumberOfInstalledAppsResult(int num);
     }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index a967abf..1dd0471 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -50,6 +50,7 @@
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settings.search.SearchIndexableRaw;
+import com.android.settings.widget.FooterPreference;
 import com.android.settings.widget.SwitchBar;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
@@ -97,7 +98,7 @@
 
 
     // accessed from inner class (not private to avoid thunks)
-    Preference mMyDevicePreference;
+    FooterPreference mMyDevicePreference;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -120,7 +121,7 @@
                 final Resources res = context.getResources();
                 final Locale locale = res.getConfiguration().getLocales().get(0);
                 final BidiFormatter bidiFormatter = BidiFormatter.getInstance(locale);
-                mMyDevicePreference.setSummary(res.getString(
+                mMyDevicePreference.setTitle(res.getString(
                             R.string.bluetooth_is_visible_message,
                             bidiFormatter.unicodeWrap(mLocalAdapter.getName())));
             }
@@ -171,10 +172,8 @@
         mAvailableDevicesCategory.setOrder(2);
         getPreferenceScreen().addPreference(mAvailableDevicesCategory);
 
-        mMyDevicePreference = new Preference(getPrefContext());
+        mMyDevicePreference = mFooterPreferenceMixin.createFooterPreference();
         mMyDevicePreference.setSelectable(false);
-        mMyDevicePreference.setOrder(3);
-        getPreferenceScreen().addPreference(mMyDevicePreference);
 
         setHasOptionsMenu(true);
     }
@@ -356,7 +355,7 @@
                 final Resources res = getResources();
                 final Locale locale = res.getConfiguration().getLocales().get(0);
                 final BidiFormatter bidiFormatter = BidiFormatter.getInstance(locale);
-                mMyDevicePreference.setSummary(res.getString(
+                mMyDevicePreference.setTitle(res.getString(
                             R.string.bluetooth_is_visible_message,
                             bidiFormatter.unicodeWrap(mLocalAdapter.getName())));
 
diff --git a/src/com/android/settings/core/lifecycle/Lifecycle.java b/src/com/android/settings/core/lifecycle/Lifecycle.java
index c47f97e..9a42cd9 100644
--- a/src/com/android/settings/core/lifecycle/Lifecycle.java
+++ b/src/com/android/settings/core/lifecycle/Lifecycle.java
@@ -18,6 +18,7 @@
 import android.annotation.UiThread;
 import android.content.Context;
 import android.os.Bundle;
+import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settings.core.lifecycle.events.OnAttach;
 import com.android.settings.core.lifecycle.events.OnCreate;
@@ -27,6 +28,7 @@
 import com.android.settings.core.lifecycle.events.OnSaveInstanceState;
 import com.android.settings.core.lifecycle.events.OnStart;
 import com.android.settings.core.lifecycle.events.OnStop;
+import com.android.settings.core.lifecycle.events.SetPreferenceScreen;
 import com.android.settings.utils.ThreadUtils;
 
 import java.util.ArrayList;
@@ -73,6 +75,14 @@
         }
     }
 
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        for (LifecycleObserver observer : mObservers) {
+            if (observer instanceof SetPreferenceScreen) {
+                ((SetPreferenceScreen) observer).setPreferenceScreen(preferenceScreen);
+            }
+        }
+    }
+
     public void onResume() {
         for (LifecycleObserver observer : mObservers) {
             if (observer instanceof OnResume) {
diff --git a/src/com/android/settings/core/lifecycle/ObservablePreferenceFragment.java b/src/com/android/settings/core/lifecycle/ObservablePreferenceFragment.java
index f55b183..0a1a628 100644
--- a/src/com/android/settings/core/lifecycle/ObservablePreferenceFragment.java
+++ b/src/com/android/settings/core/lifecycle/ObservablePreferenceFragment.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceScreen;
 
 /**
  * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
@@ -46,6 +47,12 @@
         super.onCreate(savedInstanceState);
     }
 
+    @Override
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        mLifecycle.setPreferenceScreen(preferenceScreen);
+        super.setPreferenceScreen(preferenceScreen);
+    }
+
     @CallSuper
     @Override
     public void onSaveInstanceState(Bundle outState) {
diff --git a/src/com/android/settings/core/lifecycle/events/SetPreferenceScreen.java b/src/com/android/settings/core/lifecycle/events/SetPreferenceScreen.java
new file mode 100644
index 0000000..d206ed3
--- /dev/null
+++ b/src/com/android/settings/core/lifecycle/events/SetPreferenceScreen.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 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.settings.core.lifecycle.events;
+
+import android.support.v7.preference.PreferenceScreen;
+
+public interface SetPreferenceScreen {
+
+    void setPreferenceScreen(PreferenceScreen preferenceScreen);
+}
diff --git a/src/com/android/settings/fingerprint/FingerprintSettings.java b/src/com/android/settings/fingerprint/FingerprintSettings.java
index 58060f3..0f48f8c 100644
--- a/src/com/android/settings/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/fingerprint/FingerprintSettings.java
@@ -17,7 +17,6 @@
 package com.android.settings.fingerprint;
 
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -26,7 +25,6 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.hardware.fingerprint.Fingerprint;
@@ -49,15 +47,12 @@
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.TextPaint;
-import android.text.method.LinkMovementMethod;
 import android.text.style.URLSpan;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.EditText;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -68,6 +63,8 @@
 import com.android.settings.SubSettings;
 import com.android.settings.Utils;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.widget.FooterPreference;
+import com.android.settings.widget.FooterPreferenceMixin;
 import com.android.settingslib.HelpUtils;
 import com.android.settingslib.RestrictedLockUtils;
 
@@ -298,21 +295,14 @@
                 mLaunchedConfirm = true;
                 launchChooseOrConfirmLock();
             }
-        }
 
-        @Override
-        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
-            super.onViewCreated(view, savedInstanceState);
-            TextView v = (TextView) LayoutInflater.from(view.getContext()).inflate(
-                    R.layout.fingerprint_settings_footer, null);
-            EnforcedAdmin admin = RestrictedLockUtils.checkIfKeyguardFeaturesDisabled(
-                    getActivity(), DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId);
-            v.setText(LearnMoreSpan.linkify(getText(admin != null
+            final FooterPreference pref = mFooterPreferenceMixin.createFooterPreference();
+            final EnforcedAdmin admin = RestrictedLockUtils.checkIfKeyguardFeaturesDisabled(
+                    activity, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId);
+            pref.setTitle(LearnMoreSpan.linkify(getText(admin != null
                             ? R.string.security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled
                             : R.string.security_settings_fingerprint_enroll_disclaimer),
                     getString(getHelpResource()), admin));
-            v.setMovementMethod(new LinkMovementMethod());
-            setFooterView(v);
         }
 
         protected void removeFingerprintPreference(int fingerprintId) {
diff --git a/src/com/android/settings/widget/FooterPreference.java b/src/com/android/settings/widget/FooterPreference.java
new file mode 100644
index 0000000..4a0d128
--- /dev/null
+++ b/src/com/android/settings/widget/FooterPreference.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.settings.widget;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.method.LinkMovementMethod;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * A custom preference acting as "footer" of a page. It has a field for icon and text. It is added
+ * to screen as the last preference.
+ */
+public class FooterPreference extends Preference {
+
+    static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
+    static final String KEY_FOOTER = "footer_preference";
+
+    public FooterPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    public FooterPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public FooterPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public FooterPreference(Context context) {
+        super(context);
+        init();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        TextView title = (TextView) holder.itemView.findViewById(android.R.id.title);
+        title.setMovementMethod(new LinkMovementMethod());
+    }
+
+    private void init() {
+        setLayoutResource(R.layout.preference_footer);
+        setIcon(R.drawable.ic_info_outline_24dp);
+        setKey(KEY_FOOTER);
+        setOrder(ORDER_FOOTER);
+    }
+}
diff --git a/src/com/android/settings/widget/FooterPreferenceMixin.java b/src/com/android/settings/widget/FooterPreferenceMixin.java
new file mode 100644
index 0000000..53e3d75
--- /dev/null
+++ b/src/com/android/settings/widget/FooterPreferenceMixin.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.settings.widget;
+
+import android.content.Context;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.core.lifecycle.Lifecycle;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.SetPreferenceScreen;
+
+public class FooterPreferenceMixin implements LifecycleObserver, SetPreferenceScreen {
+
+    private final PreferenceFragment mFragment;
+    private FooterPreference mFooterPreference;
+
+    public FooterPreferenceMixin(PreferenceFragment fragment, Lifecycle lifecycle) {
+        mFragment = fragment;
+        lifecycle.addObserver(this);
+    }
+
+    @Override
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        if (mFooterPreference != null) {
+            preferenceScreen.addPreference(mFooterPreference);
+        }
+    }
+
+    /**
+     * Creates a new {@link FooterPreference}.
+     */
+    public FooterPreference createFooterPreference() {
+        final PreferenceScreen screen = mFragment.getPreferenceScreen();
+        if (mFooterPreference != null && screen != null) {
+            screen.removePreference(mFooterPreference);
+        }
+        mFooterPreference = new FooterPreference(getPrefContext());
+
+        if (screen != null) {
+            screen.addPreference(mFooterPreference);
+        }
+        return mFooterPreference;
+    }
+
+    /**
+     * Returns an UI context with theme properly set for new Preference objects.
+     */
+    private Context getPrefContext() {
+        return mFragment.getPreferenceManager().getContext();
+    }
+
+    public boolean hasFooter() {
+        return mFooterPreference != null;
+    }
+}
+