Introduce DisplayNavigationBarController

DisplayNavigationBarController is a class to control everything related
to external navigation bars. Belows are its tasks:
1. create external nav bars when:
   a. StatusBar.createNavigationBar()
   b. A system decoration supported display added
2. remove external nav bars when display is removed.
3. remove all external nav bars when StatusBar.destroy()

(Following Tasks)
1. Support SystemUiVisualibility for external nav bars
   (corresponding to b/117478341#3)
2. A design doc for refactor

Bug: 115978725
Bug: 117478341
Test: atest SystemUITests
Test: Manual - external nav bars still works

Change-Id: I17084c1be287ae7ccfb94b2d1302072a5d620e29
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b7844bc..fbcf068 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -44,14 +44,15 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginInitializerImpl;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.power.PowerNotificationWarnings;
 import com.android.systemui.power.PowerUI;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.shared.plugins.PluginManagerImpl;
+import com.android.systemui.statusbar.DisplayNavigationBarController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
@@ -362,6 +363,9 @@
         mProviders.put(AppOpsController.class, () ->
                 new AppOpsControllerImpl(mContext, getDependency(BG_LOOPER)));
 
+        mProviders.put(DisplayNavigationBarController.class, () ->
+                new DisplayNavigationBarController(mContext, getDependency(MAIN_HANDLER)));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisplayNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/DisplayNavigationBarController.java
new file mode 100644
index 0000000..3b611a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisplayNavigationBarController.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 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.systemui.statusbar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManagerGlobal;
+
+import com.android.systemui.statusbar.phone.NavigationBarFragment;
+
+/**
+ * A controller to handle external navigation bars
+ */
+public class DisplayNavigationBarController implements DisplayListener {
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final DisplayManager mDisplayManager;
+
+    /** A displayId - nav bar mapping */
+    private SparseArray<NavigationBarFragment> mExternalNavigationBarMap = new SparseArray<>();
+
+    public DisplayNavigationBarController(Context context, Handler handler) {
+        mContext = context;
+        mHandler = handler;
+        mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+
+        registerListener();
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        final Display display = mDisplayManager.getDisplay(displayId);
+        addExternalNavigationBar(display);
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        final NavigationBarFragment navBar = mExternalNavigationBarMap.get(displayId);
+        if (navBar != null) {
+            final View navigationView = navBar.getView().getRootView();
+            WindowManagerGlobal.getInstance().removeView(navigationView, true);
+            mExternalNavigationBarMap.remove(displayId);
+        }
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+    }
+
+    /** Create external navigation bars when car/status bar initializes */
+    public void createNavigationBars() {
+        // Add external navigation bars if more than one displays exist.
+        final Display[] displays = mDisplayManager.getDisplays();
+        for (Display display : displays) {
+            addExternalNavigationBar(display);
+        }
+    }
+
+    /** remove external navigation bars and unset everything related to external navigation bars */
+    public void destroy() {
+        unregisterListener();
+        if (mExternalNavigationBarMap.size() > 0) {
+            for (int i = 0; i < mExternalNavigationBarMap.size(); i++) {
+                final View navigationWindow = mExternalNavigationBarMap.valueAt(i)
+                        .getView().getRootView();
+                WindowManagerGlobal.getInstance()
+                        .removeView(navigationWindow, true /* immediate */);
+            }
+            mExternalNavigationBarMap.clear();
+        }
+    }
+
+    private void registerListener() {
+        mDisplayManager.registerDisplayListener(this, mHandler);
+    }
+
+    private void unregisterListener() {
+        mDisplayManager.unregisterDisplayListener(this);
+    }
+
+    /**
+     * Add a phone navigation bar on an external display if the display supports system decorations.
+     *
+     * @param display the display to add navigation bar on
+     */
+    private void addExternalNavigationBar(Display display) {
+        if (display == null || display.getDisplayId() == DEFAULT_DISPLAY
+                || !display.supportsSystemDecorations()) {
+            return;
+        }
+
+        final int displayId = display.getDisplayId();
+        final Context externalDisplayContext = mContext.createDisplayContext(display);
+        NavigationBarFragment.create(externalDisplayContext,
+                (tag, fragment) -> {
+                    final NavigationBarFragment navBar = (NavigationBarFragment) fragment;
+                    // TODO(b/115978725): handle external nav bars sysuiVisibility
+                    navBar.setCurrentSysuiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
+                    mExternalNavigationBarMap.append(displayId, navBar);
+                }
+        );
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index bc3638e..4bff5ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -21,7 +21,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
-import android.view.Display;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -269,13 +268,7 @@
         buildNavBarContent();
         attachNavBarWindows();
 
-        // Add external navigation bars if more than one displays exist.
-        final Display[] displays = mDisplayManager.getDisplays();
-
-        for (Display display : displays) {
-            // TODO(115978725): Add phone navigationBar for now
-            addExternalNavigationBar(display);
-        }
+        mNavigationBarController.createNavigationBars();
     }
 
     private void buildNavBarContent() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 966a346..12fbf2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -22,7 +22,6 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -72,7 +71,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
 import android.media.AudioAttributes;
 import android.metrics.LogMaker;
 import android.net.Uri;
@@ -99,7 +97,6 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
@@ -172,6 +169,7 @@
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.DisplayNavigationBarController;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
@@ -567,32 +565,6 @@
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
 
-    /** A displayId - nav bar mapping */
-    private SparseArray<NavigationBarFragment> mExternalNavigationBarMap = new SparseArray<>();
-
-    // TODO(b/115978725): Move it to DisplayNavigationBarController
-    private final DisplayListener mDisplayListener = new DisplayListener() {
-        @Override
-        public void onDisplayAdded(int displayId) {
-            final Display display = mDisplayManager.getDisplay(displayId);
-            addExternalNavigationBar(display);
-        }
-
-        @Override
-        public void onDisplayRemoved(int displayId) {
-            final NavigationBarFragment navBar = mExternalNavigationBarMap.get(displayId);
-            if (navBar != null) {
-                final View navigationView = navBar.getView().getRootView();
-                WindowManagerGlobal.getInstance().removeView(navigationView, true);
-                mExternalNavigationBarMap.remove(displayId);
-            }
-        }
-
-        @Override
-        public void onDisplayChanged(int displayId) {
-        }
-    };
-
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private boolean mVibrateOnOpening;
     private VibratorHelper mVibratorHelper;
@@ -639,6 +611,7 @@
         mKeyguardViewMediator = getComponent(KeyguardViewMediator.class);
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+        mNavigationBarController = Dependency.get(DisplayNavigationBarController.class);
 
         mColorExtractor.addOnColorsChangedListener(this);
         mStatusBarStateController.addListener(this, StatusBarStateController.RANK_STATUS_BAR);
@@ -650,9 +623,6 @@
         mDisplay = mWindowManager.getDefaultDisplay();
         updateDisplaySize();
 
-        // get display service to detect display status
-        mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-
         Resources res = mContext.getResources();
         mVibrateOnOpening = mContext.getResources().getBoolean(
                 R.bool.config_vibrateOnIconAnimation);
@@ -695,7 +665,6 @@
             // If the system process isn't there we're doomed anyway.
         }
 
-        mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
         createAndAddWindows();
 
         // Make sure we always have the most current wallpaper info.
@@ -1084,33 +1053,6 @@
             }
             mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
         });
-
-        // Add external navigation bars if more than one displays exist.
-        final Display[] displays = mDisplayManager.getDisplays();
-        for (Display display : displays) {
-            addExternalNavigationBar(display);
-        }
-    }
-
-    /**
-     * Add a phone navigation bar on an external display if the display supports system decorations.
-     *
-     * @param display the display to add navigation bar on
-     */
-    protected void addExternalNavigationBar(Display display) {
-        if (display == null || display.getDisplayId() == DEFAULT_DISPLAY
-                || !display.supportsSystemDecorations()) {
-            return;
-        }
-
-        final int displayId = display.getDisplayId();
-        final Context externalDisplayContext = mContext.createDisplayContext(display);
-        NavigationBarFragment.create(externalDisplayContext,
-                (tag, fragment) -> {
-                    final NavigationBarFragment navBar = (NavigationBarFragment) fragment;
-                    navBar.setCurrentSysuiVisibility(mSystemUiVisibility);
-                    mExternalNavigationBarMap.append(displayId, navBar);
-                });
     }
 
     /**
@@ -2925,16 +2867,7 @@
             mWindowManager.removeViewImmediate(mNavigationBarView);
             mNavigationBarView = null;
         }
-        mDisplayManager.unregisterDisplayListener(mDisplayListener);
-        if (mExternalNavigationBarMap.size() > 0) {
-            for (int i = 0; i < mExternalNavigationBarMap.size(); i++) {
-                final View navigationWindow = mExternalNavigationBarMap.valueAt(i)
-                        .getView().getRootView();
-                WindowManagerGlobal.getInstance()
-                        .removeView(navigationWindow, true /* immediate */);
-            }
-            mExternalNavigationBarMap.clear();
-        }
+        mNavigationBarController.destroy();
         mContext.unregisterReceiver(mBroadcastReceiver);
         mContext.unregisterReceiver(mDemoReceiver);
         mAssistManager.destroy();
@@ -4179,6 +4112,8 @@
     private DeviceProvisionedController mDeviceProvisionedController
             = Dependency.get(DeviceProvisionedController.class);
 
+    protected DisplayNavigationBarController mNavigationBarController;
+
     // UI-specific methods
 
     protected WindowManager mWindowManager;