Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 59f1744e authored by Victor Chan's avatar Victor Chan Committed by Android (Google) Code Review
Browse files

Merge "Highlight car nav facet when recent task changes."

parents 11a27b19 1c6d0589
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
<!--
  ~ 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
  -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="48.0dp"
        android:height="48.0dp"
        android:viewportWidth="48.0"
        android:viewportHeight="48.0">
    <path
        android:fillColor="#FFFFFFFF"
        android:pathData="M14.0,20.0l10.0,10.0 10.0,-10.0z"/>
    <path
        android:pathData="M0 0h48v48H0z"
        android:fillColor="#00000000"/>
</vector>
+42 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 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.
*/
-->

<com.android.systemui.statusbar.car.CarNavigationButton
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">
    <com.android.keyguard.AlphaOptimizedImageButton
            android:id="@+id/car_nav_button_icon"
            android:layout_height="match_parent"
            android:layout_width="wrap_content"
            android:layout_centerInParent="true"
            android:animateLayoutChanges="true">
    </com.android.keyguard.AlphaOptimizedImageButton>

    <com.android.keyguard.AlphaOptimizedImageButton
            android:id="@+id/car_nav_button_more_icon"
            android:layout_height="match_parent"
            android:layout_width="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/car_nav_button_icon"
            android:animateLayoutChanges="true">
    </com.android.keyguard.AlphaOptimizedImageButton>
</com.android.systemui.statusbar.car.CarNavigationButton>
+5 −3
Original line number Diff line number Diff line
@@ -22,7 +22,9 @@
         isn't a longpress action associated with a shortcut item, put in an empty item to make
         sure everything lines up.
    -->
    <array name="car_shortcut_icons" />
    <array name="car_shortcut_intent_uris" />
    <array name="car_shortcut_longpress_intent_uris" />
    <array name="car_facet_icons" />
    <array name="car_facet_intent_uris" />
    <array name="car_facet_longpress_intent_uris" />
    <array name="car_facet_package_filters"/>
    <array name="car_facet_category_filters"/>
</resources>
+275 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.statusbar.car;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v4.util.SimpleArrayMap;
import android.view.View;
import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.ActivityStarter;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

/**
 * A controller to populate data for CarNavigationBarView and handle user interactions.
 * <p/>
 * Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can customize
 * the navigation buttons by updating arrays_car.xml appropriately in an overlay.
 */
class CarNavigationBarController {

    // Each facet of the navigation bar maps to a set of package names or categories defined in
    // arrays_car.xml. Package names for a given facet are delimited by ";"
    private static final String FACET_FILTER_DEMILITER = ";";

    private Context mContext;
    private CarNavigationBarView mNavBar;
    private ActivityStarter mActivityStarter;

    // Set of categories each facet will filter on.
    private List<String[]> mFacetCategories = new ArrayList<String[]>();
    // Set of package names each facet will filter on.
    private List<String[]> mFacetPackages = new ArrayList<String[]>();

    private SimpleArrayMap<String, Integer> mFacetCategoryMap
            = new SimpleArrayMap<String, Integer>();
    private SimpleArrayMap<String, Integer> mFacetPackageMap
            = new SimpleArrayMap<String, Integer>();

    private List<Intent> mIntents = new ArrayList<Intent>();
    private List<Intent> mLongPressIntents = new ArrayList<Intent>();

    private List<CarNavigationButton> mNavButtons = new ArrayList<CarNavigationButton>();

    private int mCurrentFacetIndex;

    public CarNavigationBarController(Context context,
                                      CarNavigationBarView navBar,
                                      ActivityStarter activityStarter) {
        mContext = context;
        mNavBar = navBar;
        mActivityStarter = activityStarter;
        bind();
    }

    public void taskChanged(String packageName) {
        // If the package name belongs to a filter, then highlight appropriate button in
        // the navigation bar.
        if (mFacetPackageMap.containsKey(packageName)) {
            setCurrentFacet(mFacetPackageMap.get(packageName));
        }

        // Check if the package matches any of the categories for the facets
        String category = getPackageCategory(packageName);
        if (category != null) {
            setCurrentFacet(mFacetCategoryMap.get(category));
        }
    }

    private void bind() {
        // Read up arrays_car.xml and populate the navigation bar here.
        Resources r = mContext.getResources();
        TypedArray icons = r.obtainTypedArray(R.array.car_facet_icons);
        TypedArray intents = r.obtainTypedArray(R.array.car_facet_intent_uris);
        TypedArray longpressIntents =
                r.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
        TypedArray facetPackageNames = r.obtainTypedArray(R.array.car_facet_package_filters);

        TypedArray facetCategories = r.obtainTypedArray(R.array.car_facet_category_filters);

        if (icons.length() != intents.length()
                || icons.length() != longpressIntents.length()
                || icons.length() != facetPackageNames.length()
                || icons.length() != facetCategories.length()) {
            throw new RuntimeException("car_facet array lengths do not match");
        }

        for (int i = 0; i < icons.length(); i++) {
            Drawable icon = icons.getDrawable(i);
            try {
                mIntents.add(i,
                        Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME));

                String longpressUri = longpressIntents.getString(i);
                boolean hasLongpress = !longpressUri.isEmpty();
                if (hasLongpress) {
                    mLongPressIntents.add(i,
                            Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME));
                }

                CarNavigationButton button = createNavButton(icon, i, hasLongpress);
                mNavButtons.add(button);
                mNavBar.addButton(button, createNavButton(icon, i, hasLongpress));

                initFacetFilterMaps(i,
                        facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
                        facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
            } catch (URISyntaxException e) {
                throw new RuntimeException("Malformed intent uri", e);
            }
        }
    }

    private void initFacetFilterMaps(int id, String[] packageNames, String[] categories){
        mFacetCategories.add(categories);
        for (int i = 0; i < categories.length; i++) {
            mFacetCategoryMap.put(categories[i], id);
        }

        mFacetPackages.add(packageNames);
        for (int i = 0; i < packageNames.length; i++) {
            mFacetPackageMap.put(packageNames[i], id);
        }
    }

    private String getPackageCategory(String packageName) {
        PackageManager pm = mContext.getPackageManager();
        int size = mFacetCategories.size();
        // For each facet, check if the given package name matches one of its categories
        for (int i = 0; i < size; i++) {
            String[] categories = mFacetCategories.get(i);
            for (int j = 0; j < categories.length; j++) {
                String category = categories[j];
                Intent intent = new Intent();
                intent.setPackage(packageName);
                intent.setAction(Intent.ACTION_MAIN);
                intent.addCategory(category);
                List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
                if (list.size() > 0) {
                    // Cache this package name into facetPackageMap, so we won't have to query
                    // all categories next time this package name shows up.
                    mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category));
                    return category;
                }
            }
        }
        return null;
    }

    /**
     * Helper method to check if a given facet has multiple packages associated with it.
     * This can be resource defined package names or package names filtered by facet category.
     */
    private boolean facetHasMultiplePackages(int index) {
        PackageManager pm = mContext.getPackageManager();

        // Check if the packages defined for the filter actually exists on the device
        String[] packages = mFacetPackages.get(index);
        if (packages.length > 1) {
            int count = 0;
            for (int i = 0; i < packages.length; i++) {
                count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0;
                if (count > 1) {
                    return true;
                }
            }
        }

        // If there weren't multiple packages defined for the facet, check the categories
        // and see if they resolve to multiple package names
        String categories[] = mFacetCategories.get(index);

        int count = 0;
        for (int i = 0; i < categories.length; i++) {
            String category = categories[i];
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_MAIN);
            intent.addCategory(category);
            count += pm.queryIntentActivities(intent, 0).size();
            if (count > 1) {
                return true;
            }
        }
        return false;
    }

    private void setCurrentFacet(int index) {
        if (index == mCurrentFacetIndex) {
            return;
        }

        if (mNavButtons.get(mCurrentFacetIndex) != null) {
            mNavButtons.get(mCurrentFacetIndex)
                    .setSelected(false /* selected */, false /* showMoreIcon */);
        }

        if (mNavButtons.get(index) != null) {
            mNavButtons.get(index).setSelected(true /* selected */,
                    facetHasMultiplePackages(index)  /* showMoreIcon */);
        }
        mCurrentFacetIndex = index;
    }

    private CarNavigationButton createNavButton(Drawable icon, final int id,
                                                boolean longClickEnabled) {
        CarNavigationButton button = (CarNavigationButton) View.inflate(mContext,
                R.layout.car_navigation_button, null);
        button.setResources(icon);
        LinearLayout.LayoutParams lp =
                new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
        button.setLayoutParams(lp);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setCurrentFacet(id);
                onFacetClicked(id);
            }
        });

        if (longClickEnabled) {
            button.setLongClickable(true);
            button.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    onFacetLongClicked(id);
                    setCurrentFacet(id);
                    return true;
                }
            });
        } else {
            button.setLongClickable(false);
        }
        return button;
    }

    private void startActivity(Intent intent) {
        if (mActivityStarter != null && intent != null) {
            mActivityStarter.startActivity(intent, true);
        }
    }

    private void onFacetClicked(int index) {
        // TODO: determine what data to pass to the trampoline, so it can start
        // the default app or the lens picker.
        startActivity(mIntents.get(index));
    }

    private void onFacetLongClicked(int index) {
        // TODO: determine what data to pass to the trampoline, so it can start
        // the default app or the lens picker.
        startActivity(mLongPressIntents.get(index));
    }
}
+11 −88
Original line number Diff line number Diff line
@@ -17,36 +17,29 @@
package com.android.systemui.statusbar.car;

import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.R.color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;

import com.android.systemui.R;
import com.android.systemui.statusbar.phone.ActivityStarter;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.policy.KeyButtonView;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * A custom navigation bar for the automotive use case.
 * <p>
 * The navigation bar in the automotive use case is more like a list of shortcuts, which we
 * expect to be customizable by the car OEMs. This implementation populates the nav_buttons layout
 * from resources rather than the layout file so customization would then mean updating
 * arrays_car.xml appropriately in an overlay.
 * The navigation bar in the automotive use case is more like a list of shortcuts, rendered
 * in a linear layout.
 */
class CarNavigationBarView extends NavigationBarView {
    private ActivityStarter mActivityStarter;
    private LinearLayout mNavButtons;
    private LinearLayout mLightsOutButtons;

    public CarNavigationBarView(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -54,83 +47,13 @@ class CarNavigationBarView extends NavigationBarView {

    @Override
    public void onFinishInflate() {
        // Read up arrays_car.xml and populate the navigation bar here.
        Context context = getContext();
        Resources r = getContext().getResources();
        TypedArray icons = r.obtainTypedArray(R.array.car_shortcut_icons);
        TypedArray intents = r.obtainTypedArray(R.array.car_shortcut_intent_uris);
        TypedArray longpressIntents =
                r.obtainTypedArray(R.array.car_shortcut_longpress_intent_uris);

        if (icons.length() != intents.length()) {
            throw new RuntimeException("car_shortcut_icons and car_shortcut_intents do not match");
        }

        LinearLayout navButtons = (LinearLayout) findViewById(R.id.nav_buttons);
        LinearLayout lightsOut = (LinearLayout) findViewById(R.id.lights_out);

        for (int i = 0; i < icons.length(); i++) {
            Drawable icon = icons.getDrawable(i);

            try {
                Intent intent = Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME);
                Intent longpress = null;
                String longpressUri = longpressIntents.getString(i);
                if (!longpressUri.isEmpty()) {
                    longpress = Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME);
                }

                // nav_buttons and lights_out should match exactly.
                navButtons.addView(makeButton(context, icon, intent, longpress));
                lightsOut.addView(makeButton(context, icon, intent, longpress));
            } catch (URISyntaxException e) {
                throw new RuntimeException("Malformed intent uri", e);
            }
        }
    }

    private ImageButton makeButton(Context context, Drawable icon,
            final Intent intent, final Intent longpress) {
        ImageButton button = new ImageButton(context);

        button.setImageDrawable(icon);
        button.setScaleType(ScaleType.CENTER);
        button.setBackgroundColor(color.transparent);
        LinearLayout.LayoutParams lp =
                new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
        button.setLayoutParams(lp);

        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mActivityStarter != null) {
                    mActivityStarter.startActivity(intent, true);
                }
            }
        });

        // Long click handlers are optional.
        if (longpress != null) {
            button.setLongClickable(true);
            button.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if (mActivityStarter != null) {
                        mActivityStarter.startActivity(longpress, true);
                        return true;
                    }
                    return false;
                }
            });
        } else {
            button.setLongClickable(false);
        }

        return button;
        mNavButtons = (LinearLayout) findViewById(R.id.nav_buttons);
        mLightsOutButtons = (LinearLayout) findViewById(R.id.lights_out);
    }

    public void setActivityStarter(ActivityStarter activityStarter) {
        mActivityStarter = activityStarter;
    public void addButton(CarNavigationButton button, CarNavigationButton lightsOutButton){
        mNavButtons.addView(button);
        mLightsOutButtons.addView(lightsOutButton);
    }

    @Override
Loading