Add a secondary launcher activity (1/2)

This secondary launcher activity with new category SECONDARY_HOME and
multiple instance supporrted could be used on sencondary display.

Bug: 118206886
Bug: 114329798
Test: Manual
Change-Id: Ibaecf8ef7614389760d0fcc547ef6d378a921583
This commit is contained in:
Chilun 2018-11-29 20:38:22 +08:00 committed by Chilun Huang
parent 4d64da3259
commit 3516bd89d9
24 changed files with 1243 additions and 1 deletions

View File

@ -108,7 +108,9 @@ include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
LOCAL_STATIC_ANDROID_LIBRARIES := \
Launcher3CommonDepsLib \
SecondaryDisplayLauncherLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_shortcuts_overrides) \

View File

@ -0,0 +1,39 @@
#
# 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_ANDROID_LIBRARIES := androidx.design_design
LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
LOCAL_MODULE := SecondaryDisplayLauncherLib
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_STATIC_JAVA_LIBRARY)

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 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.
*/
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher3">
<application>
<activity
android:name="com.android.launcher3.SecondaryDisplayLauncher"
android:theme="@style/SecondaryLauncherTheme"
android:launchMode="singleTop"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SECONDARY_HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,22 @@
<?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.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
</vector>

View File

@ -0,0 +1,22 @@
<?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.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>

View File

@ -0,0 +1,56 @@
<?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.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/launcher_bg_color"
android:fitsSystemWindows="true" >
<GridView
android:id="@+id/pinned_app_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/app_grid_margin_top"
android:layout_marginStart="@dimen/app_grid_margin_left"
android:layout_marginEnd="@dimen/app_grid_margin_right"
android:columnWidth="@dimen/app_list_col_width"
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
android:numColumns="auto_fit" />
<ImageButton
android:id="@+id/OptionsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_marginStart="@dimen/options_button_margin"
android:layout_marginBottom="@dimen/options_button_margin"
android:src="@drawable/ic_settings"
android:background="@null"/>
<FrameLayout
android:layout_width="@dimen/app_picker_width"
android:layout_height="@dimen/app_picker_height"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/app_picker_fab_margin">
<include layout="@layout/app_picker_layout"/>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,57 @@
<?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.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/launcher_bg_color"
android:fitsSystemWindows="true" >
<GridView
android:id="@+id/pinned_app_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/app_grid_margin_top"
android:layout_marginStart="@dimen/app_grid_margin_left"
android:layout_marginEnd="@dimen/app_grid_margin_right"
android:columnWidth="@dimen/app_list_col_width"
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
android:numColumns="auto_fit" />
<ImageButton
android:id="@+id/OptionsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_marginStart="@dimen/options_button_margin"
android:layout_marginBottom="@dimen/options_button_margin"
android:src="@drawable/ic_settings"
android:background="@null"/>
<FrameLayout
android:layout_width="@dimen/app_picker_width"
android:layout_height="@dimen/app_picker_height"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/app_picker_fab_margin"
android:layout_marginBottom="@dimen/app_picker_fab_margin">
<include layout="@layout/app_picker_layout"/>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,35 @@
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@dimen/app_icon_width"
android:layout_height="@dimen/app_icon_height" />
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1" />
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="@+id/picker_app_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/app_grid_margin_top"
android:layout_marginStart="@dimen/app_grid_margin_left"
android:layout_marginEnd="@dimen/app_grid_margin_right"
android:columnWidth="@dimen/app_list_col_width"
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
android:numColumns="auto_fit" />
</FrameLayout>

View File

@ -0,0 +1,46 @@
<?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.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.circularreveal.cardview.CircularRevealCardView
android:id="@+id/FloatingSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible">
<GridView
android:id="@+id/app_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/app_grid_margin_top"
android:columnWidth="@dimen/app_list_col_width"
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
android:numColumns="auto_fit" />
</android.support.design.circularreveal.cardview.CircularRevealCardView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:src="@drawable/ic_apps"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,56 @@
<?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.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/launcher_bg_color"
android:fitsSystemWindows="true" >
<GridView
android:id="@+id/pinned_app_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/app_grid_margin_top"
android:layout_marginStart="@dimen/app_grid_margin_left"
android:layout_marginEnd="@dimen/app_grid_margin_right"
android:columnWidth="@dimen/app_list_col_width"
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
android:numColumns="auto_fit" />
<ImageButton
android:id="@+id/OptionsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_marginStart="@dimen/options_button_margin"
android:layout_marginBottom="@dimen/options_button_margin"
android:src="@drawable/ic_settings"
android:background="@null"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/app_picker_fab_margin">
<include layout="@layout/app_picker_layout"/>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,23 @@
<?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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/add_app_shortcut"
android:title="@string/add_app_shortcut" />
<item android:id="@+id/set_wallpaper"
android:title="@string/set_wallpaper" />
</menu>

View File

@ -0,0 +1,22 @@
<?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.
-->
<resources>
<dimen name="app_picker_width">400dp</dimen>
<dimen name="app_picker_height">400dp</dimen>
<dimen name="app_picker_fab_margin">60dp</dimen>
</resources>

View File

@ -0,0 +1,22 @@
<?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.
-->
<resources>
<dimen name="app_picker_width">660dp</dimen>
<dimen name="app_picker_height">660dp</dimen>
<dimen name="app_picker_fab_margin">70dp</dimen>
</resources>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/colors.xml
**
** Copyright 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.
*/
-->
<resources>
<color name="launcher_bg_color">#884e8391</color>
</resources>

View File

@ -0,0 +1,31 @@
<?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.
-->
<resources>
<dimen name="app_list_col_width">72dp</dimen>
<dimen name="app_list_horizontal_spacing">24dp</dimen>
<dimen name="app_list_vertical_spacing">24dp</dimen>
<dimen name="app_icon_width">64dp</dimen>
<dimen name="app_icon_height">64dp</dimen>
<dimen name="app_grid_margin_top">24dp</dimen>
<dimen name="app_grid_margin_left">8dp</dimen>
<dimen name="app_grid_margin_right">8dp</dimen>
<dimen name="app_picker_width">300dp</dimen>
<dimen name="app_picker_height">300dp</dimen>
<dimen name="app_picker_fab_margin">20dp</dimen>
<dimen name="options_button_margin">20dp</dimen>
</resources>

View File

@ -0,0 +1,25 @@
<?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.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">SecondaryDisplayLauncher</string>
<string name="couldnt_launch">Couldn\'t launch the activity</string>
<string name="add_app_shortcut">Add app shortcut</string>
<string name="set_wallpaper">Set wallpaper</string>
</resources>

View File

@ -0,0 +1,30 @@
<?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.
*/
-->
<resources>
<style name="SecondaryLauncherTheme" parent="Theme.AppCompat.Light.NoActionBar" >
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
</resources>

View File

@ -0,0 +1,58 @@
/**
* 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.launcher3;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
/** An entry that represents a single activity that can be launched. */
public class AppEntry {
private String mLabel;
private Drawable mIcon;
private Intent mLaunchIntent;
AppEntry(ResolveInfo info, PackageManager packageManager) {
mLabel = info.loadLabel(packageManager).toString();
mIcon = info.loadIcon(packageManager);
mLaunchIntent = new Intent();
mLaunchIntent.setComponent(new ComponentName(info.activityInfo.packageName,
info.activityInfo.name));
}
String getLabel() {
return mLabel;
}
Drawable getIcon() {
return mIcon;
}
Intent getLaunchIntent() { return mLaunchIntent; }
ComponentName getComponentName() {
return mLaunchIntent.getComponent();
}
@Override
public String toString() {
return mLabel;
}
}

View File

@ -0,0 +1,63 @@
/**
* 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.launcher3;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.launcher3.R;
import java.util.List;
/** Adapter for available apps list. */
public class AppListAdapter extends ArrayAdapter<AppEntry> {
private final LayoutInflater mInflater;
AppListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
void setData(List<AppEntry> data) {
clear();
if (data != null) {
addAll(data);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.app_grid_item, parent, false);
} else {
view = convertView;
}
AppEntry item = getItem(position);
((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(item.getIcon());
((TextView)view.findViewById(R.id.app_name)).setText(item.getLabel());
return view;
}
}

View File

@ -0,0 +1,126 @@
/**
* 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.launcher3;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.ArrayList;
import java.util.List;
/**
* A view model that provides a list of activities that can be launched.
*/
public class AppListViewModel extends AndroidViewModel {
private final AppListLiveData mLiveData;
private final PackageIntentReceiver
mPackageIntentReceiver;
public AppListViewModel(Application application) {
super(application);
mLiveData = new AppListLiveData(application);
mPackageIntentReceiver = new PackageIntentReceiver(mLiveData, application);
}
public LiveData<List<AppEntry>> getAppList() {
return mLiveData;
}
protected void onCleared() {
getApplication().unregisterReceiver(mPackageIntentReceiver);
}
}
class AppListLiveData extends LiveData<List<AppEntry>> {
private final PackageManager mPackageManager;
private int mCurrentDataVersion;
public AppListLiveData(Context context) {
mPackageManager = context.getPackageManager();
loadData();
}
void loadData() {
final int loadDataVersion = ++mCurrentDataVersion;
new AsyncTask<Void, Void, List<AppEntry>>() {
@Override
protected List<AppEntry> doInBackground(Void... voids) {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
PackageManager.GET_META_DATA);
List<AppEntry> entries = new ArrayList<>();
if (apps != null) {
for (ResolveInfo app : apps) {
AppEntry entry = new AppEntry(app, mPackageManager);
entries.add(entry);
}
}
return entries;
}
@Override
protected void onPostExecute(List<AppEntry> data) {
if (mCurrentDataVersion == loadDataVersion) {
setValue(data);
}
}
}.execute();
}
}
/**
* Receiver used to notify live data about app list changes.
*/
class PackageIntentReceiver extends BroadcastReceiver {
private final AppListLiveData mLiveData;
public PackageIntentReceiver(AppListLiveData liveData, Context context) {
mLiveData = liveData;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
context.registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
context.registerReceiver(this, sdFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
mLiveData.loadData();
}
}

View File

@ -0,0 +1,120 @@
/**
* 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.launcher3;
import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* A view model that provides a list of activities that were pinned by user to always display on
* home screen.
* The pinned activities are stored in {@link SharedPreferences} to keep the sample simple :).
*/
public class PinnedAppListViewModel extends AndroidViewModel {
final static String PINNED_APPS_KEY = "pinned_apps";
private final PinnedAppListLiveData mLiveData;
public PinnedAppListViewModel(Application application) {
super(application);
mLiveData = new PinnedAppListLiveData(application);
}
public LiveData<List<AppEntry>> getPinnedAppList() {
return mLiveData;
}
}
class PinnedAppListLiveData extends LiveData<List<AppEntry>> {
private final Context mContext;
private final PackageManager mPackageManager;
// Store listener reference, so it won't be GC-ed.
private final SharedPreferences.OnSharedPreferenceChangeListener mChangeListener;
private int mCurrentDataVersion;
public PinnedAppListLiveData(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
final SharedPreferences prefs = context.getSharedPreferences(PINNED_APPS_KEY, 0);
mChangeListener = (preferences, key) -> {
loadData();
};
prefs.registerOnSharedPreferenceChangeListener(mChangeListener);
loadData();
}
private void loadData() {
final int loadDataVersion = ++mCurrentDataVersion;
new AsyncTask<Void, Void, List<AppEntry>>() {
@Override
protected List<AppEntry> doInBackground(Void... voids) {
List<AppEntry> entries = new ArrayList<>();
final SharedPreferences sp = mContext.getSharedPreferences(PINNED_APPS_KEY, 0);
final Set<String> pinnedAppsComponents = sp.getStringSet(PINNED_APPS_KEY, null);
if (pinnedAppsComponents == null) {
return null;
}
for (String componentString : pinnedAppsComponents) {
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.setComponent(ComponentName.unflattenFromString(componentString));
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
PackageManager.GET_META_DATA);
if (apps != null) {
for (ResolveInfo app : apps) {
final AppEntry entry = new AppEntry(app, mPackageManager);
entries.add(entry);
}
}
}
return entries;
}
@Override
protected void onPostExecute(List<AppEntry> data) {
if (mCurrentDataVersion == loadDataVersion) {
setValue(data);
}
}
}.execute();
}
}

View File

@ -0,0 +1,74 @@
/**
* 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.launcher3;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import androidx.fragment.app.DialogFragment;
import com.android.launcher3.R;
/**
* Callback to be invoked when an app was picked.
*/
interface AppPickedCallback {
void onAppPicked(AppEntry appEntry);
}
/**
* Dialog that provides the user with a list of available apps to pin to the home screen.
*/
public class PinnedAppPickerDialog extends DialogFragment {
private AppListAdapter mAppListAdapter;
private AppPickedCallback mAppPickerCallback;
public PinnedAppPickerDialog() {
}
public static PinnedAppPickerDialog newInstance(AppListAdapter appListAdapter,
AppPickedCallback callback) {
PinnedAppPickerDialog
frag = new PinnedAppPickerDialog();
frag.mAppListAdapter = appListAdapter;
frag.mAppPickerCallback = callback;
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.app_picker_dialog, container);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
GridView appGridView = view.findViewById(R.id.picker_app_grid);
appGridView.setAdapter(mAppListAdapter);
appGridView.setOnItemClickListener((adapterView, itemView, position, id) -> {
final AppEntry entry = mAppListAdapter.getItem(position);
mAppPickerCallback.onAppPicked(entry);
dismiss();
});
}
}

View File

@ -0,0 +1,217 @@
/**
* 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.launcher3;
import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.AlertDialog;
import android.app.Application;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.design.circularreveal.cardview.CircularRevealCardView;
import android.support.design.widget.FloatingActionButton;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
import java.util.HashSet;
import java.util.Set;
/**
* Secondary launcher activity. It's launch mode is configured as "singleTop" to allow showing on
* multiple displays and to ensure a single instance per each display.
*/
public class SecondaryDisplayLauncher extends FragmentActivity implements AppPickedCallback,
PopupMenu.OnMenuItemClickListener {
private AppListAdapter mAppListAdapter;
private AppListAdapter mPinnedAppListAdapter;
private CircularRevealCardView mAppDrawerView;
private FloatingActionButton mFab;
private boolean mAppDrawerShown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.secondary_display_launcher);
mAppDrawerView = findViewById(R.id.FloatingSheet);
mFab = findViewById(R.id.FloatingActionButton);
mFab.setOnClickListener((View v) -> {
showAppDrawer(true);
});
final ViewModelProvider viewModelProvider = new ViewModelProvider(getViewModelStore(),
new AndroidViewModelFactory((Application) getApplicationContext()));
mPinnedAppListAdapter = new AppListAdapter(this);
final GridView pinnedAppGridView = findViewById(R.id.pinned_app_grid);
pinnedAppGridView.setAdapter(mPinnedAppListAdapter);
pinnedAppGridView.setOnItemClickListener((adapterView, view, position, id) -> {
final AppEntry entry = mPinnedAppListAdapter.getItem(position);
launch(entry.getLaunchIntent());
});
final PinnedAppListViewModel pinnedAppListViewModel =
viewModelProvider.get(PinnedAppListViewModel.class);
pinnedAppListViewModel.getPinnedAppList().observe(this, data -> {
mPinnedAppListAdapter.setData(data);
});
mAppListAdapter = new AppListAdapter(this);
final GridView appGridView = findViewById(R.id.app_grid);
appGridView.setAdapter(mAppListAdapter);
appGridView.setOnItemClickListener((adapterView, view, position, id) -> {
final AppEntry entry = mAppListAdapter.getItem(position);
launch(entry.getLaunchIntent());
});
final AppListViewModel appListViewModel = viewModelProvider.get(AppListViewModel.class);
appListViewModel.getAppList().observe(this, data -> {
mAppListAdapter.setData(data);
});
ImageButton optionsButton = findViewById(R.id.OptionsButton);
optionsButton.setOnClickListener((View v) -> {
PopupMenu popup = new PopupMenu(this,v);
popup.setOnMenuItemClickListener(this);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.context_menu, popup.getMenu());
popup.show();
});
}
@Override
public boolean onMenuItemClick(MenuItem item) {
// Respond to picking one of the popup menu items.
final int id = item.getItemId();
if (id == R.id.add_app_shortcut) {
FragmentManager fm = getSupportFragmentManager();
PinnedAppPickerDialog pickerDialogFragment =
PinnedAppPickerDialog.newInstance(mAppListAdapter, this);
pickerDialogFragment.show(fm, "fragment_app_picker");
return true;
} else if (id == R.id.set_wallpaper) {
Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
startActivity(Intent.createChooser(intent, getString(R.string.set_wallpaper)));
return true;
}
return true;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
showAppDrawer(false);
}
public void onBackPressed() {
// If the app drawer was shown - hide it. Otherwise, not doing anything since we don't want
// to close the launcher.
showAppDrawer(false);
}
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// A new intent will bring the launcher to top. Hide the app drawer to reset the state.
showAppDrawer(false);
}
void launch(Intent launchIntent) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(launchIntent);
} catch (Exception e) {
final AlertDialog.Builder builder =
new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
builder.setTitle(R.string.couldnt_launch)
.setMessage(e.getLocalizedMessage())
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
}
/**
* Store the picked app to persistent pinned list and update the loader.
*/
@Override
public void onAppPicked(AppEntry appEntry) {
final SharedPreferences sp = getSharedPreferences(PINNED_APPS_KEY, 0);
Set<String> pinnedApps = sp.getStringSet(PINNED_APPS_KEY, null);
if (pinnedApps == null) {
pinnedApps = new HashSet<String>();
} else {
// Always need to create a new object to make sure that the changes are persisted.
pinnedApps = new HashSet<String>(pinnedApps);
}
pinnedApps.add(appEntry.getComponentName().flattenToString());
final SharedPreferences.Editor editor = sp.edit();
editor.putStringSet(PINNED_APPS_KEY, pinnedApps);
editor.apply();
}
/**
* Show/hide app drawer card with animation.
*/
private void showAppDrawer(boolean show) {
if (show == mAppDrawerShown) {
return;
}
final Animator animator = revealAnimator(mAppDrawerView, show);
if (show) {
mAppDrawerShown = true;
mAppDrawerView.setVisibility(View.VISIBLE);
mFab.setVisibility(View.INVISIBLE);
} else {
mAppDrawerShown = false;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mAppDrawerView.setVisibility(View.INVISIBLE);
mFab.setVisibility(View.VISIBLE);
}
});
}
animator.start();
}
/**
* Create reveal/hide animator for app list card.
*/
private Animator revealAnimator(View view, boolean open) {
final int radius = (int) Math.hypot((double) view.getWidth(), (double) view.getHeight());
return ViewAnimationUtils.createCircularReveal(view, view.getRight(), view.getBottom(),
open ? 0 : radius, open ? radius : 0);
}
}