Snap for 6439596 from 7e14a22f019f8d49bb982c61709eee8db5652503 to qt-aml-tzdata-release
Change-Id: I43d5a03d1876bcd82ae4d3754edaa5b5bb2f206c
diff --git a/Android.mk b/Android.mk
index 28fd418..4e1658f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -51,7 +51,8 @@
LOCAL_STATIC_ANDROID_LIBRARIES += \
androidx-constraintlayout_constraintlayout \
androidx.lifecycle_lifecycle-extensions \
- car-media-common
+ car-media-common \
+ car-ui-lib
# Including the resources for the static android libraries allows to pick up their static overlays.
LOCAL_RESOURCE_DIR += \
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6b3044a..f084f80 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -55,12 +55,7 @@
<activity
android:name=".AppGridActivity"
android:launchMode="singleInstance"
- android:noHistory="true">
- <meta-data android:name="distractionOptimized" android:value="true"/>
- </activity>
- <activity
- android:name=".AppSearchActivity"
- android:launchMode="singleInstance"
+ android:exported="true"
android:noHistory="true">
<meta-data android:name="distractionOptimized" android:value="true"/>
</activity>
diff --git a/OWNERS b/OWNERS
deleted file mode 100644
index 1686b3c..0000000
--- a/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# Default code reviewers picked from top 3 or more developers.
-# Please update this list if you find better candidates.
-qiaozhang@google.com
-davidln@google.com
-ajchen@google.com
-arnaudberry@google.com
-stenning@google.com
diff --git a/res/layout/app_grid_activity.xml b/res/layout/app_grid_activity.xml
index af50bf1..e5d644d 100644
--- a/res/layout/app_grid_activity.xml
+++ b/res/layout/app_grid_activity.xml
@@ -27,10 +27,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
- <com.android.car.apps.common.widget.PagedRecyclerView
+ <com.android.car.ui.recyclerview.CarUiRecyclerView
android:id="@+id/apps_grid"
+ app:layoutStyle="grid"
+ app:numOfColumns="4"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
+ android:layout_height="match_parent"/>
</LinearLayout>
diff --git a/res/layout/app_grid_activity_header.xml b/res/layout/app_grid_activity_header.xml
index cc38658..36aa10c 100644
--- a/res/layout/app_grid_activity_header.xml
+++ b/res/layout/app_grid_activity_header.xml
@@ -48,37 +48,13 @@
</FrameLayout>
<TextView
+ android:id="@+id/title"
style="@style/TitleText"
android:layout_width="wrap_content"
android:layout_height="@dimen/app_bar_height"
android:layout_toEndOf="@id/exit_button_container"
android:gravity="center_vertical"
- android:text="@string/all_apps"
- />
-
- <FrameLayout
- android:id="@+id/search_button_container"
- android:layout_width="@dimen/panel_margin"
- android:layout_height="@dimen/app_grid_touch_target_size"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- android:background="?android:attr/selectableItemBackground"
- android:clickable="true"
- android:focusable="true"
- android:visibility="gone"
- >
- <ImageButton
- android:layout_width="@dimen/icon_size"
- android:layout_height="@dimen/icon_size"
- android:layout_gravity="center"
- android:adjustViewBounds="true"
- android:background="@null"
- android:clickable="false"
- android:focusable="false"
- android:scaleType="fitXY"
- android:src="@drawable/ic_search_black"
- android:tint="@color/icon_tint"/>
- </FrameLayout>
+ android:text="@string/app_launcher_title_all_apps"/>
<View
style="@style/HorizontalLineDivider"
diff --git a/res/layout/app_search_activity.xml b/res/layout/app_search_activity.xml
deleted file mode 100644
index 6b17641..0000000
--- a/res/layout/app_search_activity.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?android:attr/colorPrimary"
- android:gravity="center_horizontal"
- android:orientation="vertical">
-
- <RelativeLayout
- android:id="@+id/app_search_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/app_bar_height">
-
- <FrameLayout
- android:id="@+id/exit_button_container"
- android:layout_width="@dimen/panel_margin"
- android:layout_height="@dimen/app_grid_touch_target_size"
- android:background="?android:attr/selectableItemBackground"
- android:clickable="true"
- android:focusable="true">
- <ImageButton
- android:layout_width="@dimen/icon_size"
- android:layout_height="@dimen/icon_size"
- android:layout_gravity="center"
- android:adjustViewBounds="true"
- android:background="@null"
- android:clickable="false"
- android:focusable="false"
- android:gravity="center"
- android:src="@drawable/ic_arrow_back_black"
- android:tint="@color/icon_tint"/>
- </FrameLayout>
-
- <EditText
- android:id="@+id/app_search_bar"
- android:layout_width="@dimen/search_item_width"
- android:layout_height="@dimen/search_box_height"
- android:layout_centerInParent="true"
- android:layout_gravity="center_vertical"
- android:background="?android:attr/colorPrimary"
- android:drawablePadding="@dimen/search_bar_drawable_text_padding"
- android:drawableStart="@drawable/ic_search_black"
- android:drawableTint="@color/icon_tint"
- android:inputType="text|textNoSuggestions"
- android:maxLines="1"
- android:paddingEnd="@dimen/search_bar_margin"
- android:paddingStart="@dimen/search_bar_margin"
- android:textAppearance="?android:attr/textAppearanceLarge"/>
- </RelativeLayout>
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/search_result"
- android:layout_width="@dimen/search_item_width"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/search_result_margin"/>
-
-</LinearLayout>
-
diff --git a/res/layout/app_search_result_item.xml b/res/layout/app_search_result_item.xml
deleted file mode 100644
index 5fdfcc1..0000000
--- a/res/layout/app_search_result_item.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/app_item"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?android:attr/selectableItemBackground"
- android:minHeight="@*android:dimen/car_single_line_list_item_height"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@+id/app_icon"
- android:layout_width="@dimen/icon_size"
- android:layout_height="@dimen/icon_size"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:layout_marginStart="@dimen/search_bar_margin"
- android:layout_marginEnd="@dimen/search_bar_margin" />
-
- <TextView
- android:id="@+id/app_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:layout_marginStart="@dimen/search_result_text_margin"
- android:layout_marginEnd="@dimen/search_result_text_margin"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:ellipsize="end"
- android:maxLines="1" />
-</RelativeLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 57e5376..a46a2af 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -16,7 +16,9 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_title">Car Launcher</string>
- <string name="all_apps">All apps</string>
+
+ <string name="app_launcher_title_all_apps">All apps</string>
+ <string name="app_launcher_title_media_only">Media apps</string>
<string name="driving_toast_text">
<xliff:g id="app_name" example="Settings">%1$s</xliff:g> can\'t be used while driving.
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 5422778..3001aff 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -16,8 +16,7 @@
-->
<resources>
- <!--Theme for the app, it's defined empty here so it can be overlaid easily -->
- <style name="Theme.Launcher" parent="android:Theme.DeviceDefault.Wallpaper.NoTitleBar">
+ <style name="Theme.Launcher" parent="android:Theme.DeviceDefault.NoActionBar">
<item name="textAppearanceGridItem">@android:style/TextAppearance.DeviceDefault.Medium</item>
<item name="textAppearanceGridItemSecondary">@android:style/TextAppearance.DeviceDefault.Small</item>
</style>
diff --git a/src/com/android/car/carlauncher/AppGridActivity.java b/src/com/android/car/carlauncher/AppGridActivity.java
index b0cb0d4..616bb5c 100644
--- a/src/com/android/car/carlauncher/AppGridActivity.java
+++ b/src/com/android/car/carlauncher/AppGridActivity.java
@@ -16,7 +16,9 @@
package com.android.car.carlauncher;
-import android.annotation.Nullable;
+import static com.android.car.carlauncher.AppLauncherUtils.APP_TYPE_LAUNCHABLES;
+import static com.android.car.carlauncher.AppLauncherUtils.APP_TYPE_MEDIA_SERVICES;
+
import android.app.Activity;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
@@ -32,13 +34,18 @@
import android.content.ServiceConnection;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
+import android.widget.TextView;
+import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
import androidx.recyclerview.widget.RecyclerView;
@@ -57,12 +64,13 @@
* Launcher activity that shows a grid of apps.
*/
public final class AppGridActivity extends Activity {
-
private static final String TAG = "AppGridActivity";
+ private static final String MODE_INTENT_EXTRA = "com.android.car.carlauncher.mode";
private int mColumnNumber;
private boolean mShowAllApps = true;
private final Set<String> mHiddenApps = new HashSet<>();
+ private final Set<String> mCustomMediaComponents = new HashSet<>();
private AppGridAdapter mGridAdapter;
private PackageManager mPackageManager;
private UsageStatsManager mUsageStatsManager;
@@ -70,6 +78,30 @@
private Car mCar;
private CarUxRestrictionsManager mCarUxRestrictionsManager;
private CarPackageManager mCarPackageManager;
+ private Mode mMode;
+
+ private enum Mode {
+ ALL_APPS(R.string.app_launcher_title_all_apps,
+ APP_TYPE_LAUNCHABLES + APP_TYPE_MEDIA_SERVICES,
+ true),
+ MEDIA_ONLY(R.string.app_launcher_title_media_only,
+ APP_TYPE_MEDIA_SERVICES,
+ true),
+ MEDIA_POPUP(R.string.app_launcher_title_media_only,
+ APP_TYPE_MEDIA_SERVICES,
+ false),
+ ;
+ public final @StringRes int mTitleStringId;
+ public final @AppLauncherUtils.AppTypes int mAppTypes;
+ public final boolean mOpenMediaCenter;
+
+ Mode(@StringRes int titleStringId, @AppLauncherUtils.AppTypes int appTypes,
+ boolean openMediaCenter) {
+ mTitleStringId = titleStringId;
+ mAppTypes = appTypes;
+ mOpenMediaCenter = openMediaCenter;
+ }
+ }
private ServiceConnection mCarConnectionListener = new ServiceConnection() {
@Override
@@ -108,23 +140,19 @@
mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
mCar = Car.createCar(this, mCarConnectionListener);
mHiddenApps.addAll(Arrays.asList(getResources().getStringArray(R.array.hidden_apps)));
+ mCustomMediaComponents.addAll(
+ Arrays.asList(getResources().getStringArray(R.array.custom_media_packages)));
setContentView(R.layout.app_grid_activity);
+ updateMode();
+
View exitView = findViewById(R.id.exit_button_container);
exitView.setOnClickListener(v -> finish());
- exitView.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- mShowAllApps = !mShowAllApps;
- updateAppsLists();
- return true;
- }
- });
-
- findViewById(R.id.search_button_container).setOnClickListener((View view) -> {
- Intent intent = new Intent(this, AppSearchActivity.class);
- startActivity(intent);
+ exitView.setOnLongClickListener(v -> {
+ mShowAllApps = !mShowAllApps;
+ updateAppsLists();
+ return true;
});
mGridAdapter = new AppGridAdapter(this);
@@ -138,11 +166,37 @@
}
});
gridView.setLayoutManager(gridLayoutManager);
-
gridView.setAdapter(mGridAdapter);
}
@Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ updateMode();
+ }
+
+ private void updateMode() {
+ mMode = parseMode(getIntent());
+ TextView titleView = findViewById(R.id.title);
+ titleView.setText(mMode.mTitleStringId);
+ }
+
+ /**
+ * Note: This activity is exported, meaning that it might receive intents from any source.
+ * Intent data parsing must be extra careful.
+ */
+ @NonNull
+ private Mode parseMode(@Nullable Intent intent) {
+ String mode = intent != null ? intent.getStringExtra(MODE_INTENT_EXTRA) : null;
+ try {
+ return mode != null ? Mode.valueOf(mode) : Mode.ALL_APPS;
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Received invalid mode: " + mode, e);
+ }
+ }
+
+ @Override
protected void onResume() {
super.onResume();
// Using onResume() to refresh most recently used apps because we want to refresh even if
@@ -153,8 +207,13 @@
/** Updates the list of all apps, and the list of the most recently used ones. */
private void updateAppsLists() {
Set<String> blackList = mShowAllApps ? Collections.emptySet() : mHiddenApps;
- LauncherAppsInfo appsInfo = AppLauncherUtils.getAllLauncherApps(blackList,
- getSystemService(LauncherApps.class), mCarPackageManager, mPackageManager);
+ LauncherAppsInfo appsInfo = AppLauncherUtils.getLauncherApps(blackList,
+ mCustomMediaComponents,
+ mMode.mAppTypes,
+ mMode.mOpenMediaCenter,
+ getSystemService(LauncherApps.class),
+ mCarPackageManager,
+ mPackageManager);
mGridAdapter.setAllApps(appsInfo.getLaunchableComponentsList());
mGridAdapter.setMostRecentApps(getMostRecentApps(appsInfo));
}
diff --git a/src/com/android/car/carlauncher/AppItemViewHolder.java b/src/com/android/car/carlauncher/AppItemViewHolder.java
index ff3b651..418bb85 100644
--- a/src/com/android/car/carlauncher/AppItemViewHolder.java
+++ b/src/com/android/car/carlauncher/AppItemViewHolder.java
@@ -65,9 +65,12 @@
isLaunchable ? R.dimen.app_icon_opacity : R.dimen.app_icon_opacity_unavailable));
if (isLaunchable) {
- mAppItem.setOnClickListener(v -> AppLauncherUtils.launchApp(mContext, app));
- mAppItem.setLongClickable(app.getAlternateLaunchIntent() != null);
- mAppItem.setOnLongClickListener(v-> openAlternateLaunchIntent(app));
+ mAppItem.setOnClickListener(v -> app.getLaunchCallback().accept(mContext));
+ mAppItem.setLongClickable(app.getAlternateLaunchCallback() != null);
+ mAppItem.setOnLongClickListener(v-> {
+ app.getAlternateLaunchCallback().accept(mContext);
+ return true;
+ });
} else {
String warningText = mContext.getResources()
.getString(R.string.driving_toast_text, app.getDisplayName());
@@ -75,13 +78,4 @@
v -> Toast.makeText(mContext, warningText, Toast.LENGTH_LONG).show());
}
}
-
- private boolean openAlternateLaunchIntent(AppMetaData app) {
- Intent intent = app.getAlternateLaunchIntent();
- if (intent != null) {
- mContext.startActivity(intent);
- return true;
- }
- return false;
- }
}
diff --git a/src/com/android/car/carlauncher/AppLauncherUtils.java b/src/com/android/car/carlauncher/AppLauncherUtils.java
index 91ccb44..223a551 100644
--- a/src/com/android/car/carlauncher/AppLauncherUtils.java
+++ b/src/com/android/car/carlauncher/AppLauncherUtils.java
@@ -16,11 +16,15 @@
package com.android.car.carlauncher;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.annotation.Nullable;
+import android.app.Activity;
import android.app.ActivityOptions;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.content.pm.CarPackageManager;
+import android.car.media.CarMediaManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -33,8 +37,12 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.car.media.common.source.MediaSourceViewModel;
+
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -50,6 +58,12 @@
class AppLauncherUtils {
private static final String TAG = "AppLauncherUtils";
+ @Retention(SOURCE)
+ @IntDef({APP_TYPE_LAUNCHABLES, APP_TYPE_MEDIA_SERVICES})
+ @interface AppTypes {}
+ static final int APP_TYPE_LAUNCHABLES = 1;
+ static final int APP_TYPE_MEDIA_SERVICES = 2;
+
private AppLauncherUtils() {
}
@@ -65,26 +79,26 @@
*
* @param app the requesting app's AppMetaData
*/
- static void launchApp(Context context, AppMetaData app) {
+ static void launchApp(Context context, Intent intent) {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(context.getDisplayId());
- context.startActivity(app.getMainLaunchIntent(), options.toBundle());
+ context.startActivity(intent, options.toBundle());
}
/** Bundles application and services info. */
static class LauncherAppsInfo {
/*
* Map of all car launcher components' (including launcher activities and media services)
- * metadata keyed by ComponentName.
+ * metadata keyed by ComponentName.
*/
private final Map<ComponentName, AppMetaData> mLaunchables;
/** Map of all the media services keyed by ComponentName. */
private final Map<ComponentName, ResolveInfo> mMediaServices;
- LauncherAppsInfo(@NonNull Map<ComponentName, AppMetaData> components,
+ LauncherAppsInfo(@NonNull Map<ComponentName, AppMetaData> launchablesMap,
@NonNull Map<ComponentName, ResolveInfo> mediaServices) {
- mLaunchables = components;
+ mLaunchables = launchablesMap;
mMediaServices = mediaServices;
}
@@ -143,15 +157,24 @@
* Gets all the components that we want to see in the launcher in unsorted order, including
* launcher activities and media services.
*
- * @param blackList A (possibly empty) list of apps (package names) to hide
- * @param launcherApps The {@link LauncherApps} system service
- * @param carPackageManager The {@link CarPackageManager} system service
- * @param packageManager The {@link PackageManager} system service
+ * @param blackList A (possibly empty) list of apps (package names) to hide
+ * @param customMediaComponents A (possibly empty) list of media components (component names)
+ * that shouldn't be shown in Launcher because their applications'
+ * launcher activities will be shown
+ * @param appTypes Types of apps to show (e.g.: all, or media sources only)
+ * @param openMediaCenter Whether launcher should navigate to media center when the
+ * user selects a media source.
+ * @param launcherApps The {@link LauncherApps} system service
+ * @param carPackageManager The {@link CarPackageManager} system service
+ * @param packageManager The {@link PackageManager} system service
* @return a new {@link LauncherAppsInfo}
*/
@NonNull
- static LauncherAppsInfo getAllLauncherApps(
+ static LauncherAppsInfo getLauncherApps(
@NonNull Set<String> blackList,
+ @NonNull Set<String> customMediaComponents,
+ @AppTypes int appTypes,
+ boolean openMediaCenter,
LauncherApps launcherApps,
CarPackageManager carPackageManager,
PackageManager packageManager) {
@@ -166,73 +189,114 @@
List<LauncherActivityInfo> availableActivities =
launcherApps.getActivityList(null, Process.myUserHandle());
- Map<ComponentName, AppMetaData> components = new HashMap<>(
+ Map<ComponentName, AppMetaData> launchablesMap = new HashMap<>(
mediaServices.size() + availableActivities.size());
Map<ComponentName, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size());
- Set<String> mediaPackages = new HashSet<>();
// Process media services
- for (ResolveInfo info : mediaServices) {
- String packageName = info.serviceInfo.packageName;
- String className = info.serviceInfo.name;
- ComponentName componentName = new ComponentName(packageName, className);
- mediaServicesMap.put(componentName, info);
- if (shouldAdd(componentName, components, blackList)) {
- mediaPackages.add(packageName);
- final boolean isDistractionOptimized = true;
+ if ((appTypes & APP_TYPE_MEDIA_SERVICES) != 0) {
+ for (ResolveInfo info : mediaServices) {
+ String packageName = info.serviceInfo.packageName;
+ String className = info.serviceInfo.name;
+ ComponentName componentName = new ComponentName(packageName, className);
+ mediaServicesMap.put(componentName, info);
+ if (shouldAddToLaunchables(componentName, blackList, customMediaComponents,
+ appTypes, APP_TYPE_MEDIA_SERVICES)) {
+ final boolean isDistractionOptimized = true;
- Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE);
- intent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString());
+ Intent intent = new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE);
+ intent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString());
- AppMetaData appMetaData = new AppMetaData(
+ AppMetaData appMetaData = new AppMetaData(
info.serviceInfo.loadLabel(packageManager),
componentName,
info.serviceInfo.loadIcon(packageManager),
isDistractionOptimized,
- intent,
- packageManager.getLaunchIntentForPackage(packageName));
- components.put(componentName, appMetaData);
+ context -> {
+ if (openMediaCenter) {
+ AppLauncherUtils.launchApp(context, intent);
+ } else {
+ selectMediaSourceAndFinish(context, componentName);
+ }
+ },
+ context -> AppLauncherUtils.launchApp(context,
+ packageManager.getLaunchIntentForPackage(packageName)));
+ launchablesMap.put(componentName, appMetaData);
+ }
}
}
// Process activities
- for (LauncherActivityInfo info : availableActivities) {
- ComponentName componentName = info.getComponentName();
- String packageName = componentName.getPackageName();
- // If a media service has been added to the map, don't add the activity belonging to the
- // same package.
- if (mediaPackages.contains(packageName)) {
- continue;
- }
- if (shouldAdd(componentName, components, blackList)) {
- boolean isDistractionOptimized =
+ if ((appTypes & APP_TYPE_LAUNCHABLES) != 0) {
+ for (LauncherActivityInfo info : availableActivities) {
+ ComponentName componentName = info.getComponentName();
+ String packageName = componentName.getPackageName();
+ if (shouldAddToLaunchables(componentName, blackList, customMediaComponents,
+ appTypes, APP_TYPE_LAUNCHABLES)) {
+ boolean isDistractionOptimized =
isActivityDistractionOptimized(carPackageManager, packageName,
- info.getName());
+ info.getName());
- Intent intent = new Intent(Intent.ACTION_MAIN)
+ Intent intent = new Intent(Intent.ACTION_MAIN)
.setComponent(componentName)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- AppMetaData appMetaData = new AppMetaData(
+ AppMetaData appMetaData = new AppMetaData(
info.getLabel(),
componentName,
info.getBadgedIcon(0),
isDistractionOptimized,
- intent,
- packageManager.getLaunchIntentForPackage(packageName));
- components.put(componentName, appMetaData);
+ context -> AppLauncherUtils.launchApp(context, intent),
+ null);
+ launchablesMap.put(componentName, appMetaData);
+ }
}
}
- return new LauncherAppsInfo(components, mediaServicesMap);
+ return new LauncherAppsInfo(launchablesMap, mediaServicesMap);
}
- private static boolean shouldAdd(ComponentName componentName,
- Map<ComponentName, AppMetaData> components,
- @NonNull Set<String> blackList) {
- return !components.containsKey(componentName) && !blackList.contains(
- componentName.getPackageName());
+ private static boolean shouldAddToLaunchables(@NonNull ComponentName componentName,
+ @NonNull Set<String> blackList,
+ @NonNull Set<String> customMediaComponents,
+ @AppTypes int appTypesToShow,
+ @AppTypes int componentAppType) {
+ if (blackList.contains(componentName.getPackageName())) {
+ return false;
+ }
+ switch (componentAppType) {
+ // Process media services
+ case APP_TYPE_MEDIA_SERVICES:
+ // For a media service in customMediaComponents, if its application's launcher
+ // activity will be shown in the Launcher, don't show the service's icon in the
+ // Launcher.
+ if (customMediaComponents.contains(componentName.flattenToString())
+ && (appTypesToShow & APP_TYPE_LAUNCHABLES) != 0) {
+ return false;
+ }
+ return true;
+ // Process activities
+ case APP_TYPE_LAUNCHABLES:
+ return true;
+ default:
+ Log.e(TAG, "Invalid componentAppType : " + componentAppType);
+ return false;
+ }
+ }
+
+ private static void selectMediaSourceAndFinish(Context context, ComponentName componentName) {
+ try {
+ Car carApi = Car.createCar(context);
+ CarMediaManager manager = (CarMediaManager) carApi
+ .getCarManager(Car.CAR_MEDIA_SERVICE);
+ manager.setMediaSource(componentName);
+ if (context instanceof Activity) {
+ ((Activity) context).finish();
+ }
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ }
}
/**
diff --git a/src/com/android/car/carlauncher/AppMetaData.java b/src/com/android/car/carlauncher/AppMetaData.java
index 14c6a9f..5360df3 100644
--- a/src/com/android/car/carlauncher/AppMetaData.java
+++ b/src/com/android/car/carlauncher/AppMetaData.java
@@ -18,9 +18,12 @@
import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import java.util.function.Consumer;
+
/**
* Meta data of an app including the display name, the component name, the icon drawable, and an
* intent to either open the app or the media center (for media services).
@@ -34,8 +37,8 @@
private final ComponentName mComponentName;
private final Drawable mIcon;
private final boolean mIsDistractionOptimized;
- private final Intent mMainLaunchIntent;
- private final Intent mAlternateLaunchIntent;
+ private final Consumer<Context> mLaunchCallback;
+ private final Consumer<Context> mAlternateLaunchCallback;
/**
* AppMetaData
@@ -44,24 +47,23 @@
* @param componentName the component name
* @param icon the application's icon
* @param isDistractionOptimized whether mainLaunchIntent is safe for driving
- * @param mainLaunchIntent what to open by default (goes to the media center for media
- * apps)
- * @param alternateLaunchIntent temporary allowance for media apps that still need to show UI
- * beyond sign in and settings
+ * @param launchCallback action to execute to launch this app
+ * @param alternateLaunchCallback temporary alternative action to execute (e.g.: for media apps
+ * this allows opening their own UI).
*/
AppMetaData(
CharSequence displayName,
ComponentName componentName,
Drawable icon,
boolean isDistractionOptimized,
- Intent mainLaunchIntent,
- @Nullable Intent alternateLaunchIntent) {
+ Consumer<Context> launchCallback,
+ Consumer<Context> alternateLaunchCallback) {
mDisplayName = displayName == null ? "" : displayName.toString();
mComponentName = componentName;
mIcon = icon;
mIsDistractionOptimized = isDistractionOptimized;
- mMainLaunchIntent = mainLaunchIntent;
- mAlternateLaunchIntent = alternateLaunchIntent;
+ mLaunchCallback = launchCallback;
+ mAlternateLaunchCallback = alternateLaunchCallback;
}
public String getDisplayName() {
@@ -76,12 +78,12 @@
return mComponentName;
}
- Intent getMainLaunchIntent() {
- return mMainLaunchIntent;
+ Consumer<Context> getLaunchCallback() {
+ return mLaunchCallback;
}
- Intent getAlternateLaunchIntent() {
- return mAlternateLaunchIntent;
+ Consumer<Context> getAlternateLaunchCallback() {
+ return mAlternateLaunchCallback;
}
public Drawable getIcon() {
diff --git a/src/com/android/car/carlauncher/AppSearchActivity.java b/src/com/android/car/carlauncher/AppSearchActivity.java
deleted file mode 100644
index 2947906..0000000
--- a/src/com/android/car/carlauncher/AppSearchActivity.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * 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.car.carlauncher;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.content.pm.CarPackageManager;
-import android.car.drivingstate.CarUxRestrictionsManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-import androidx.recyclerview.widget.RecyclerView;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Activity that allows user to search in apps.
- */
-public final class AppSearchActivity extends Activity {
-
- private static final String TAG = "AppSearchActivity";
-
- private PackageManager mPackageManager;
- private SearchResultAdapter mSearchResultAdapter;
- private AppInstallUninstallReceiver mInstallUninstallReceiver;
- private Car mCar;
- private CarPackageManager mCarPackageManager;
- private CarUxRestrictionsManager mCarUxRestrictionsManager;
-
- private ServiceConnection mCarConnectionListener = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- try {
- mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
- Car.CAR_UX_RESTRICTION_SERVICE);
- mSearchResultAdapter.setIsDistractionOptimizationRequired(
- mCarUxRestrictionsManager
- .getCurrentCarUxRestrictions()
- .isRequiresDistractionOptimization());
- mCarUxRestrictionsManager.registerListener(
- restrictionInfo ->
- mSearchResultAdapter.setIsDistractionOptimizationRequired(
- restrictionInfo.isRequiresDistractionOptimization()));
-
- mCarPackageManager = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
- mSearchResultAdapter.setAllApps(getAllApps());
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Car not connected in CarConnectionListener", e);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mCarUxRestrictionsManager = null;
- mCarPackageManager = null;
- }
- };
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPackageManager = getPackageManager();
- mCar = Car.createCar(this, mCarConnectionListener);
-
- setContentView(R.layout.app_search_activity);
- findViewById(R.id.container).setOnTouchListener(
- (view, event) -> {
- hideKeyboard();
- return false;
- });
- findViewById(R.id.exit_button_container).setOnClickListener(view -> finish());
-
- RecyclerView searchResultView = findViewById(R.id.search_result);
- searchResultView.setClipToOutline(true);
- mSearchResultAdapter = new SearchResultAdapter(this);
- searchResultView.setAdapter(mSearchResultAdapter);
-
- EditText searchBar = findViewById(R.id.app_search_bar);
- searchBar.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- if (TextUtils.isEmpty(s)) {
- searchResultView.setVisibility(View.GONE);
- mSearchResultAdapter.clearResults();
- } else {
- searchResultView.setVisibility(View.VISIBLE);
- mSearchResultAdapter.getFilter().filter(s.toString());
- }
- }
- });
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- // register broadcast receiver for package installation and uninstallation
- mInstallUninstallReceiver = new AppInstallUninstallReceiver();
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- registerReceiver(mInstallUninstallReceiver, filter);
-
- // Connect to car service
- mCar.connect();
- }
-
- @Override
- protected void onStop() {
- super.onPause();
- // disconnect from app install/uninstall receiver
- if (mInstallUninstallReceiver != null) {
- unregisterReceiver(mInstallUninstallReceiver);
- mInstallUninstallReceiver = null;
- }
- // disconnect from car listeners
- try {
- if (mCarUxRestrictionsManager != null) {
- mCarUxRestrictionsManager.unregisterListener();
- }
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Error unregistering listeners", e);
- }
- if (mCar != null) {
- mCar.disconnect();
- }
- }
-
- private List<AppMetaData> getAllApps() {
- AppLauncherUtils.LauncherAppsInfo appsInfo = AppLauncherUtils.getAllLauncherApps(
- Collections.emptySet(), getSystemService(LauncherApps.class), mCarPackageManager,
- mPackageManager);
- return appsInfo.getLaunchableComponentsList();
- }
-
- public void hideKeyboard() {
- InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(
- Activity.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
- }
-
- private class AppInstallUninstallReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String packageName = intent.getData().getSchemeSpecificPart();
-
- if (TextUtils.isEmpty(packageName)) {
- Log.e(TAG, "System sent an empty app install/uninstall broadcast");
- return;
- }
-
- mSearchResultAdapter.setAllApps(getAllApps());
- }
- }
-}
diff --git a/src/com/android/car/carlauncher/SearchResultAdapter.java b/src/com/android/car/carlauncher/SearchResultAdapter.java
deleted file mode 100644
index 6844dee..0000000
--- a/src/com/android/car/carlauncher/SearchResultAdapter.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.car.carlauncher;
-
-import android.app.Activity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Filter;
-import android.widget.Filterable;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * The search result adapter that binds the filtered result to a list view.
- */
-
-final class SearchResultAdapter
- extends RecyclerView.Adapter<SearchResultAdapter.ViewHolder> implements Filterable {
- private final Activity mContext;
- private List<AppMetaData> mAllApps;
- private List<AppMetaData> mSearchResults;
- private AppSearchFilter filter;
- private boolean mIsDistractionOptimizationRequired;
-
- public SearchResultAdapter(Activity context) {
- mContext = context;
- mSearchResults = null;
- }
-
- public void setIsDistractionOptimizationRequired(boolean isDistractionOptimizationRequired) {
- mIsDistractionOptimizationRequired = isDistractionOptimizationRequired;
- notifyDataSetChanged();
- }
-
- public void setAllApps(List<AppMetaData> apps) {
- mAllApps = apps;
- notifyDataSetChanged();
- }
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
- private View mAppItem;
- private ImageView mAppIconView;
- private TextView mAppNameView;
-
- public ViewHolder(View view) {
- super(view);
- mAppItem = view.findViewById(R.id.app_item);
- mAppIconView = mAppItem.findViewById(R.id.app_icon);
- mAppNameView = mAppItem.findViewById(R.id.app_name);
- }
-
- public void bind(AppMetaData app, View.OnClickListener listener) {
- if (app == null) {
- // Empty out the view
- mAppIconView.setImageDrawable(null);
- mAppNameView.setText(null);
- mAppItem.setClickable(false);
- mAppItem.setOnClickListener(null);
- } else {
- mAppIconView.setImageDrawable(app.getIcon());
- mAppNameView.setText(app.getDisplayName());
- mAppItem.setClickable(true);
- mAppItem.setOnClickListener(listener);
- }
- }
- }
-
- /**
- * This filter does a simple case insensitive text match based on the application
- * display name. e.g. "gm" -> "gmail".
- * It only matches apps starting with the search query. It does not do sub-string matching.
- */
- private class AppSearchFilter extends Filter {
-
- @Override
- protected FilterResults performFiltering(CharSequence constraint) {
- FilterResults results = new FilterResults();
- if (constraint != null && constraint.length() > 0) {
- List filterList = new ArrayList();
- for (int i = 0; i < mAllApps.size(); i++) {
- AppMetaData app = mAllApps.get(i);
- if (shouldShowApp(
- app.getDisplayName(),
- constraint.toString(),
- app.getIsDistractionOptimized())) {
- filterList.add(app);
- }
- }
- Collections.sort(filterList, AppLauncherUtils.ALPHABETICAL_COMPARATOR);
- results.count = filterList.size();
- results.values = filterList;
- } else {
- results.count = 0;
- results.values = null;
- }
-
- return results;
- }
-
- private boolean shouldShowApp(
- String displayName, String constraint, boolean isDistractionOptimized) {
- if (!mIsDistractionOptimizationRequired
- || (mIsDistractionOptimizationRequired && isDistractionOptimized)) {
- Pattern pattern = Pattern.compile(
- "^" + constraint + ".*$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
- return pattern.matcher(displayName).matches();
- }
- return false;
- }
-
- @Override
- protected void publishResults(CharSequence constraint, FilterResults results) {
- mSearchResults = (ArrayList) results.values;
- notifyDataSetChanged();
- }
- }
-
- @Override
- public Filter getFilter() {
- if (filter == null) {
- filter = new AppSearchFilter();
- }
- return filter;
- }
-
- @NonNull
- @Override
- public SearchResultAdapter.ViewHolder onCreateViewHolder(
- @NonNull ViewGroup parent, int viewType) {
- View view = mContext.getLayoutInflater().inflate(R.layout.app_search_result_item, null);
- return new ViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(@NonNull SearchResultAdapter.ViewHolder holder, int position) {
- // Get the data item for this position
- AppMetaData app = mSearchResults.get(position);
- holder.bind(app, v -> AppLauncherUtils.launchApp(mContext, app));
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public int getItemCount() {
- if (mSearchResults != null) {
- return mSearchResults.size();
- } else {
- return 0;
- }
- }
-
- void clearResults() {
- mSearchResults.clear();
- notifyDataSetChanged();
- }
-}