Merge "P is for PAINT." into pi-dev

This commit is contained in:
TreeHugger Robot
2018-06-28 03:49:05 +00:00
committed by Android (Google) Code Review
80 changed files with 1755 additions and 3160 deletions

View File

@@ -18,6 +18,9 @@ package com.android.internal.app;
import android.animation.TimeAnimator;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -25,12 +28,15 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.View;
import android.widget.FrameLayout;
import org.json.JSONObject;
public class PlatLogoActivity extends Activity {
FrameLayout layout;
TimeAnimator anim;
@@ -87,7 +93,7 @@ public class PlatLogoActivity extends Activity {
darkest = 0;
for (int i=0; i<slots; i++) {
palette[i] = Color.HSVToColor(color);
color[0] += 360f/slots;
color[0] = (color[0] + 360f/slots) % 360f;
if (lum(palette[i]) < lum(palette[darkest])) darkest = i;
}
@@ -178,27 +184,97 @@ public class PlatLogoActivity extends Activity {
bg = new PBackground();
layout.setBackground(bg);
final ContentResolver cr = getContentResolver();
layout.setOnTouchListener(new View.OnTouchListener() {
final String TOUCH_STATS = "touch.stats";
final PointerCoords pc0 = new PointerCoords();
final PointerCoords pc1 = new PointerCoords();
double pressure_min, pressure_max;
int maxPointers;
int tapCount;
@Override
public boolean onTouch(View v, MotionEvent event) {
final float pressure = event.getPressure();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
pressure_min = pressure_max = pressure;
// fall through
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() > 1) {
if (pressure < pressure_min) pressure_min = pressure;
if (pressure > pressure_max) pressure_max = pressure;
final int pc = event.getPointerCount();
if (pc > maxPointers) maxPointers = pc;
if (pc > 1) {
event.getPointerCoords(0, pc0);
event.getPointerCoords(1, pc1);
bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
try {
final String touchDataJson = Settings.System.getString(cr, TOUCH_STATS);
final JSONObject touchData = new JSONObject(
touchDataJson != null ? touchDataJson : "{}");
if (touchData.has("min")) {
pressure_min = Math.min(pressure_min, touchData.getDouble("min"));
}
if (touchData.has("max")) {
pressure_max = Math.max(pressure_max, touchData.getDouble("max"));
}
touchData.put("min", pressure_min);
touchData.put("max", pressure_max);
Settings.System.putString(cr, TOUCH_STATS, touchData.toString());
} catch (Exception e) {
Log.e("PlatLogoActivity", "Can't write touch settings", e);
}
if (maxPointers == 1) {
tapCount ++;
if (tapCount < 7) {
bg.randomizePalette();
} else {
launchNextStage();
}
} else {
tapCount = 0;
}
maxPointers = 0;
break;
}
return true;
}
});
}
private void launchNextStage() {
final ContentResolver cr = getContentResolver();
if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) == 0) {
// For posterity: the moment this user unlocked the easter egg
try {
Settings.System.putLong(cr,
Settings.System.EGG_MODE,
System.currentTimeMillis());
} catch (RuntimeException e) {
Log.e("PlatLogoActivity", "Can't write settings", e);
}
}
try {
startActivity(new Intent(Intent.ACTION_MAIN)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addCategory("com.android.internal.category.PLATLOGO"));
} catch (ActivityNotFoundException ex) {
Log.e("PlatLogoActivity", "No more eggs.");
}
finish();
}
@Override
public void onStart() {
super.onStart();

View File

@@ -0,0 +1,33 @@
//
// 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.
//
android_app {
// the build system in pi-dev can't quite handle R.java in kt
// so we will have a mix of java and kotlin files
srcs: ["src/**/*.java", "src/**/*.kt"],
resource_dirs: ["res"],
name: "EasterEgg",
certificate: "platform",
sdk_version: "current",
optimize: {
enabled: false,
}
}

View File

@@ -1,29 +0,0 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := \
jsr305
LOCAL_STATIC_ANDROID_LIBRARIES := \
android-support-v4 \
android-support-v13 \
android-support-dynamic-animation \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
android-support-v14-preference
LOCAL_USE_AAPT2 := true
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := EasterEgg
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
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
@@ -15,85 +15,28 @@ Copyright (C) 2016 The Android Open Source Project
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.egg"
android:versionCode="1"
android:versionName="1.0">
package="com.android.egg"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="26" />
<uses-sdk android:minSdkVersion="28" />
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
<application android:label="@string/app_name" android:icon="@drawable/icon">
<activity android:name=".octo.Ocquarium"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
android:label="@string/app_name">
<activity
android:name=".paint.PaintActivity"
android:configChanges="orientation|keyboardHidden|screenSize|uiMode"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<!--<category android:name="android.intent.category.LAUNCHER" />-->
<category android:name="com.android.internal.category.PLATLOGO" />
</intent-filter>
</activity>
<!-- Android N lives on inside Android O... -->
<!-- Long press the QS tile to get here -->
<activity android:name=".neko.NekoLand"
android:theme="@android:style/Theme.Material.NoActionBar"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<!-- This is where the magic happens -->
<service
android:name=".neko.NekoService"
android:enabled="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" >
</service>
<!-- Used to show over lock screen -->
<activity android:name=".neko.NekoLockedActivity"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Material.Light.Dialog.NoActionBar"
android:showOnLockScreen="true" />
<!-- Used to enable easter egg -->
<activity android:name=".neko.NekoActivationActivity"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.NoDisplay"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- The quick settings tile, disabled by default -->
<service
android:name=".neko.NekoTile"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:icon="@drawable/stat_icon"
android:enabled="false"
android:label="@string/default_tile_name">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<!-- FileProvider for sending pictures -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.android.egg.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
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
@@ -13,10 +14,7 @@ Copyright (C) 2016 The Android Open Source Project
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="leg1" android:fillColor="#FF000000" android:pathData="M9,37h5v6h-5z"/>
</vector>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#FFFF3333" android:state_selected="true" />
<item android:color="#FFFFFFFF" />
</selector>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
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.
@@ -14,6 +14,7 @@ Copyright (C) 2016 The Android Open Source Project
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<dimen name="neko_display_size">64dp</dimen>
</resources>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#FFCC0000" android:state_selected="true" />
<item android:color="#FF000000" />
</selector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="back" android:fillColor="#FF000000" android:pathData="M37.1,22c-1.1,0 -1.9,0.8 -1.9,1.9v5.6c0,1.1 0.8,1.9 1.9,1.9H39v-1.9v-5.6V22H37.1z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="belly" android:fillColor="#FF000000" android:pathData="M20.5,25c-3.6,0 -6.5,2.9 -6.5,6.5V38h13v-6.5C27,27.9 24.1,25 20.5,25z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="cap" android:fillColor="#FF000000" android:pathData="M27.2,3.8c-1,-0.2 -2.1,-0.3 -3.2,-0.3s-2.1,0.1 -3.2,0.3c0.2,1.3 1.5,2.2 3.2,2.2C25.6,6.1 26.9,5.1 27.2,3.8z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="collar" android:fillColor="#FF000000" android:pathData="M9,18.4h30v1.7h-30z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="face_spot" android:fillColor="#FF000000" android:pathData="M19.5,15.2a4.5,3.2 0,1 0,9 0a4.5,3.2 0,1 0,-9 0z"/>
</vector>

View File

@@ -1,33 +0,0 @@
<!--
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.1,34l-3.5,1.3c-1,0.4,-2.2,-0.1,-2.6,-1.1l-1.2,-3c-0.4,-1,0.1,-2.2,1.1,-2.6l3.5,-1.3c1,-0.4,2.2,0.1,2.6,1.1l1.2,3 C20.6,32.4,20.1,33.6,19.1,34z"/>
<path
android:fillColor="#FF000000"
android:pathData="M25.2,28.1L22.9,28c-0.8,0,-1.5,-0.7,-1.4,-1.6l0.1,-2c0,-0.8,0.7,-1.5,1.6,-1.4l2.4,0.1c0.8,0,1.5,0.7,1.4,1.6l-0.1,2 C26.8,27.5,26.1,28.1,25.2,28.1z"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.7,23.1L16.5,23c-0.5,0,-0.9,-0.4,-0.8,-0.9l0.1,-2.2c0,-0.5,0.4,-0.9,0.9,-0.8l2.2,0.1c0.5,0,0.9,0.4,0.8,0.9 l-0.1,2.2C19.6,22.8,19.2,23.1,18.7,23.1z"/>
<path
android:fillColor="#FF000000"
android:pathData="M32.2,35.3l-3.6,-1.8c-1,-0.5,-1.4,-1.7,-0.9,-2.7l1.6,-3.1c0.5,-1,1.7,-1.4,2.7,-0.9l3.6,1.8c1,0.5,1.4,1.7,0.9,2.7 l-1.6,3.1C34.4,35.4,33.2,35.7,32.2,35.3z"/>
</vector>

View File

@@ -1,39 +0,0 @@
<!--
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,12v14h10V11H9z M11.7,16.3c-0.7,0,-1.3,-0.6,-1.3,-1.3s0.6,-1.3,1.3,-1.3S13,14.3,13,15S12.4,16.3,11.7,16.3z"/>
<path
android:fillColor="#FF000000"
android:pathData="M5.7,20.1l1.6,-3.0l-1.6,-3.0l4.4,3.0z"/>
<path
android:fillColor="#FF000000"
android:pathData="M19.0,6.0l-2.3,2.3l-2.7,-2.6l-2.7,2.6l-2.3,-2.3l0.0,4.0l10.0,0.0z"/>
<path
android:fillColor="#FF000000"
android:pathData="M9,25c0,8.3,6.7,15,15,15s15,-6.7,15,-15H9z M29.9,31.5h-11v-1h12L29.9,31.5z M31.9,29.5h-13v-1h14L31.9,29.5z M33.9,27.5 h-15v-1h16L33.9,27.5z"/>
<path
android:fillColor="#FF000000"
android:pathData="M27.0,38.6h2.0v6.0h-2.0z"/>
<path
android:fillColor="#FF000000"
android:pathData="M17.4,44.6l-2.1999998,0.0l4.4000006,-6.0l2.1999989,0.0z"/>
</vector>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<path
android:fillColor="#55FFFFFF"
android:fillType="evenOdd"
android:pathData="M5.71 18.29A8.99 8.99 0 0 0 22 13c0-3-1.46-5.65-3.71-7.29A8.99 8.99 0 0 0 2 11c0 3 1.46 5.65 3.71 7.29z"/>
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M7.25 19.18A8.5 8.5 0 0 0 19.19 7.24 9 9 0 0 1 7.24 19.19z"/>
<path
android:fillColor="#55FFFFFF"
android:pathData="M10.5 3a0.5 0.5 0 1 1 1 0v2.05a0.5 0.5 0 1 1-1 0V3zm3.1 0.42a0.5 0.5 0 0 1 0.93 0.39l-0.8 1.88A0.5 0.5 0 1 1 12.8 5.3l0.8-1.88zm2.7 1.57a0.5 0.5 0 1 1 0.71 0.7l-1.45 1.46a0.5 0.5 0 0 1-0.7-0.71l1.44-1.45zm1.9 2.5a0.5 0.5 0 0 1 0.38 0.92l-1.9 0.77a0.5 0.5 0 0 1-0.37-0.93l1.9-0.77zM19 10.5a0.5 0.5 0 1 1 0 1h-2.05a0.5 0.5 0 0 1 0-1H19zm-0.42 3.1a0.5 0.5 0 0 1-0.39 0.93l-1.88-0.8a0.5 0.5 0 1 1 0.39-0.92l1.88 0.8zm-1.57 2.7a0.5 0.5 0 1 1-0.7 0.71l-1.46-1.45a0.5 0.5 0 0 1 0.71-0.7l1.45 1.44zm-2.5 1.9a0.5 0.5 0 1 1-0.92 0.38l-0.77-1.9a0.5 0.5 0 0 1 0.93-0.37l0.77 1.9zM11.5 19a0.5 0.5 0 1 1-1 0v-2.05a0.5 0.5 0 0 1 1 0V19zm-3.1-0.42a0.5 0.5 0 0 1-0.93-0.39l0.8-1.88A0.5 0.5 0 0 1 9.2 16.7l-0.8 1.88zm-2.7-1.57a0.5 0.5 0 1 1-0.71-0.7l1.45-1.46a0.5 0.5 0 0 1 0.7 0.71L5.7 17.01zm-1.9-2.48a0.5 0.5 0 0 1-0.38-0.92l1.88-0.8a0.5 0.5 0 0 1 0.4 0.92l-1.9 0.8zM3 11.5a0.5 0.5 0 1 1 0-1h2.05a0.5 0.5 0 1 1 0 1H3zm0.42-3.1A0.5 0.5 0 0 1 3.8 7.46l1.88 0.8A0.5 0.5 0 1 1 5.3 9.2L3.42 8.4zm1.57-2.7a0.5 0.5 0 1 1 0.7-0.71l1.46 1.45a0.5 0.5 0 0 1-0.71 0.7L4.99 5.7zm2.5-1.9A0.5 0.5 0 0 1 8.4 3.41l0.77 1.9a0.5 0.5 0 0 1-0.93 0.37L7.48 3.8z"/>
</group>
</vector>

View File

@@ -1,24 +0,0 @@
<!--
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M24,13.8C11.3,13.8,1,18.3,1,24c0,5.7,10.3,10.2,23,10.2S47,29.7,47,24C47,18.3,36.7,13.8,24,13.8z M33.7,26.6 c1.1,-0.6,1.8,-1.3,1.8,-2c0,-2.1,-5.2,-3.8,-11.7,-3.8s-11.7,1.7,-11.7,3.8c0,0.6,0.4,1.2,1.2,1.7c-1.7,-0.8,-2.8,-1.7,-2.8,-2.8 c0,-2.5,6,-4.5,13.4,-4.5s13.4,2,13.4,4.5C37.4,24.7,36,25.8,33.7,26.6z"/>
</vector>

View File

@@ -1,24 +0,0 @@
<!--
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M24,4.5c-10.5,0,-19,8.5,-19,19s8.5,19,19,19s19,-8.5,19,-19S34.5,4.5,24,4.5z M35.2,15.5l1.6,-1.1 c0.3,-0.2,0.6,-0.1,0.8,0.1l0.1,0.1c0.2,0.3,0.1,0.6,-0.1,0.8l-1.6,1.1c-0.3,0.2,-0.6,0.1,-0.8,-0.1l-0.1,-0.1 C34.9,16.1,35,15.7,35.2,15.5z M32.7,10.7c0,-0.3,0.3,-0.5,0.6,-0.5l0.1,0c0.3,0,0.5,0.3,0.5,0.6l-0.2,2c0,0.3,-0.3,0.5,-0.6,0.5l-0.1,0 c-0.3,0,-0.5,-0.3,-0.5,-0.6L32.7,10.7z M31.7,15.1l1.5,-0.2c0.2,0,0.5,0.1,0.5,0.4l0,0.1c0,0.2,-0.1,0.5,-0.4,0.5l-1.5,0.2 c-0.2,0,-0.5,-0.1,-0.5,-0.4l0,-0.1C31.3,15.4,31.5,15.2,31.7,15.1z M28.8,10.6l1.6,-1.1c0.3,-0.2,0.6,-0.1,0.8,0.1l0.1,0.1 c0.2,0.3,0.1,0.6,-0.1,0.8l-1.6,1.1c-0.3,0.2,-0.6,0.1,-0.8,-0.1l-0.1,-0.1C28.4,11.1,28.5,10.8,28.8,10.6z M25.8,6 c0,-0.3,0.3,-0.5,0.6,-0.5l0.1,0c0.3,0,0.5,0.3,0.5,0.6l-0.2,2c0,0.3,-0.3,0.5,-0.6,0.5l-0.1,0c-0.3,0,-0.5,-0.3,-0.5,-0.6L25.8,6z M20.7,6.5l1.9,-0.7c0.3,-0.1,0.6,0,0.7,0.3l0,0.1c0.1,0.3,0,0.6,-0.3,0.7l-1.9,0.7c-0.3,0.1,-0.6,0,-0.7,-0.3l0,-0.1 C20.3,6.9,20.4,6.6,20.7,6.5z M19.9,10.9l1.5,-0.2c0.2,0,0.5,0.1,0.5,0.4l0,0.1c0,0.2,-0.1,0.5,-0.4,0.5l-1.5,0.2 c-0.2,0,-0.5,-0.1,-0.5,-0.4l0,-0.1C19.5,11.1,19.7,10.9,19.9,10.9z M16,10.9L16,10.9c0.2,-0.3,0.4,-0.4,0.6,-0.3l1.3,0.7 c0.2,0.1,0.3,0.4,0.2,0.6L18,12c-0.1,0.2,-0.4,0.3,-0.6,0.2l-1.3,-0.7C15.9,11.4,15.8,11.1,16,10.9z M15.8,18.5c0.2,0,0.4,0.1,0.5,0.4 l0,0.1c0,0.2,-0.1,0.4,-0.4,0.5l-1.5,0.2c-0.2,0,-0.4,-0.1,-0.5,-0.4l0,-0.1c0,-0.2,0.1,-0.4,0.4,-0.5L15.8,18.5z M14,21.8l-1.6,1.1 c-0.3,0.2,-0.6,0.1,-0.8,-0.1l-0.1,-0.1c-0.2,-0.3,-0.1,-0.6,0.1,-0.8l1.6,-1.1c0.3,-0.2,0.6,-0.1,0.8,0.1l0.1,0.1 C14.3,21.3,14.3,21.6,14,21.8z M12.4,12L12.4,12c0.3,-0.2,0.5,-0.2,0.7,-0.1l1,1.1c0.2,0.2,0.2,0.4,0,0.6L14,13.7 c-0.2,0.2,-0.4,0.2,-0.6,0l-1,-1.1C12.2,12.4,12.2,12.1,12.4,12z M8.3,24.5c0,0.3,-0.3,0.5,-0.6,0.5l-0.1,0c-0.3,0,-0.5,-0.3,-0.5,-0.6 l0.2,-2c0,-0.3,0.3,-0.5,0.6,-0.5l0.1,0c0.3,0,0.5,0.3,0.5,0.6L8.3,24.5z M8.5,16.2v-0.1c0,-0.3,0.2,-0.6,0.6,-0.6h2 c0.3,0,0.6,0.2,0.6,0.6v0.1c0,0.3,-0.2,0.6,-0.6,0.6H9C8.7,16.7,8.5,16.5,8.5,16.2z M10.3,20.7c-0.3,0.2,-0.6,0.1,-0.8,-0.1l-0.1,-0.1 c-0.2,-0.3,-0.1,-0.6,0.1,-0.8l1.6,-1.1c0.3,-0.2,0.6,-0.1,0.8,0.1l0.1,0.1c0.2,0.3,0.1,0.6,-0.1,0.8L10.3,20.7z M11.3,28.3l0,-0.1 c-0.1,-0.3,0,-0.6,0.3,-0.7l1.9,-0.7c0.3,-0.1,0.6,0,0.7,0.3l0,0.1c0.1,0.3,0,0.6,-0.3,0.7L12,28.6C11.7,28.7,11.4,28.6,11.3,28.3z M14.4,33c0,0.2,-0.2,0.4,-0.4,0.4h-1.5c-0.2,0,-0.4,-0.2,-0.4,-0.4v-0.1c0,-0.2,0.2,-0.4,0.4,-0.4H14c0.2,0,0.4,0.2,0.4,0.4V33z M17.9,35.2 l-1.6,1.1c-0.3,0.2,-0.6,0.1,-0.8,-0.1l-0.1,-0.1c-0.2,-0.3,-0.1,-0.6,0.1,-0.8l1.6,-1.1c0.3,-0.2,0.6,-0.1,0.8,0.1l0.1,0.1 C18.2,34.7,18.2,35.1,17.9,35.2z M20.7,33.8l-0.1,0.1c-0.1,0.3,-0.5,0.4,-0.8,0.2l-1.7,-1c-0.3,-0.1,-0.4,-0.5,-0.2,-0.8l0.1,-0.1 c0.1,-0.3,0.5,-0.4,0.8,-0.2l1.7,1C20.7,33.2,20.8,33.5,20.7,33.8z M17.5,23.5c0,-3.6,2.9,-6.5,6.5,-6.5s6.5,2.9,6.5,6.5 c0,3.6,-2.9,6.5,-6.5,6.5S17.5,27.1,17.5,23.5z M27.4,35.7l-1.9,0.7c-0.3,0.1,-0.6,0,-0.7,-0.3l0,-0.1c-0.1,-0.3,0,-0.6,0.3,-0.7l1.9,-0.7 c0.3,-0.1,0.6,0,0.7,0.3l0,0.1C27.9,35.3,27.7,35.6,27.4,35.7z M29.7,32.7l-1.4,0.5c-0.2,0.1,-0.5,0,-0.5,-0.3l0,-0.1 c-0.1,-0.2,0,-0.5,0.3,-0.5l1.4,-0.5c0.2,-0.1,0.5,0,0.5,0.3l0,0.1C30,32.3,29.9,32.6,29.7,32.7z M32.8,35.5l-0.1,0.1 c-0.1,0.3,-0.5,0.4,-0.8,0.2l-1.7,-1c-0.3,-0.1,-0.4,-0.5,-0.2,-0.8l0.1,-0.1c0.1,-0.3,0.5,-0.4,0.8,-0.2l1.7,1C32.8,34.9,32.9,35.2,32.8,35.5z M33.7,30.9c0,0.2,-0.2,0.4,-0.5,0.4l-0.1,0c-0.2,0,-0.4,-0.2,-0.4,-0.5l0.1,-1.5c0,-0.2,0.2,-0.4,0.5,-0.4l0.1,0c0.2,0,0.4,0.2,0.4,0.5 L33.7,30.9z M34.5,26.5l-1.3,0.9c-0.2,0.1,-0.5,0.1,-0.6,-0.1l-0.1,-0.1c-0.1,-0.2,-0.1,-0.5,0.1,-0.6l1.3,-0.9c0.2,-0.1,0.5,-0.1,0.6,0.1 l0.1,0.1C34.8,26.1,34.7,26.3,34.5,26.5z M35.6,20.6l-1.7,-1c-0.3,-0.1,-0.4,-0.5,-0.2,-0.8l0.1,-0.1c0.1,-0.3,0.5,-0.4,0.8,-0.2l1.7,1 c0.3,0.1,0.4,0.5,0.2,0.8l-0.1,0.1C36.2,20.6,35.8,20.7,35.6,20.6z M38.6,27.1l-1.6,1.1c-0.3,0.2,-0.6,0.1,-0.8,-0.1L36.1,28 c-0.2,-0.3,-0.1,-0.6,0.1,-0.8l1.6,-1.1c0.3,-0.2,0.6,-0.1,0.8,0.1l0.1,0.1C38.9,26.6,38.8,27,38.6,27.1z M39,19.4l-1.5,0.2 c-0.2,0,-0.5,-0.1,-0.5,-0.4l0,-0.1c0,-0.2,0.1,-0.5,0.4,-0.5l1.5,-0.2c0.2,0,0.5,0.1,0.5,0.4l0,0.1C39.4,19.1,39.2,19.3,39,19.4z"/>
</vector>

View File

@@ -1,24 +0,0 @@
<!--
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M46,18.4l-5.8,4.6c-3.9,-3.2,-8.9,-5.6,-14.6,-6.3l1.2,-6l-7.3,5.9C12.5,17.2,6.4,20,2,24.3l7.2,1.4L2,27 c4.3,4.2,10.4,7.1,17.3,7.6l3.1,2.5L22,34.8c7.1,0,13.5,-2.5,18.2,-6.5l5.8,4.6l-1.4,-7.2L46,18.4z M14.3,24.8l-0.6,0.6l-1.1,-1.1 l-1.1,1.1l-0.6,-0.6l1.1,-1.1l-1.1,-1.1l0.6,-0.6l1.1,1.1l1.1,-1.1l0.6,0.6l-1.1,1.1L14.3,24.8z M18.8,29.1c0.7,-0.8,1.1,-2.2,1.1,-3.8 c0,-1.6,-0.4,-3,-1.1,-3.8c1.1,0.5,1.9,2,1.9,3.8S19.9,28.5,18.8,29.1z M20.7,29.1c0.7,-0.8,1.1,-2.2,1.1,-3.8c0,-1.6,-0.4,-3,-1.1,-3.8 c1.1,0.5,1.9,2,1.9,3.8S21.8,28.5,20.7,29.1z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="foot1" android:fillColor="#FF000000" android:pathData="M11.5,43m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="foot2" android:fillColor="#FF000000" android:pathData="M18.5,43m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="foot3" android:fillColor="#FF000000" android:pathData="M29.5,43m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="foot4" android:fillColor="#FF000000" android:pathData="M36.5,43m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="head" android:fillColor="#FF000000" android:pathData="M9,18.5c0,-8.3 6.8,-15 15,-15s15,6.7 15,15H9z"/>
</vector>

View File

@@ -1,5 +1,5 @@
<!--
Copyright (C) 2016 The Android Open Source Project
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.
@@ -14,9 +14,14 @@ Copyright (C) 2016 The Android Open Source Project
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="bowtie" android:fillColor="#FF000000" android:pathData="M29,16.8l-10,5l0,-5l10,5z"/>
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,6.41l-1.41,-1.41l-5.59,5.59l-5.59,-5.59l-1.41,1.41l5.59,5.59l-5.59,5.59l1.41,1.41l5.59,-5.59l5.59,5.59l1.41,-1.41l-5.59,-5.59z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13.6789,5.6997L3,16.3784L3,20L4,21L7.6216,21L18.3004,10.3212L13.6789,5.6997ZM7,19L5,19L5,17L13.788,8.344L15.6561,10.212L7,19Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M20.9983,2.4982L21.5018,3.0017C22.0876,3.5875 22.0876,4.5373 21.5018,5.1231L18.1231,8.5018C17.5373,9.0876 16.5875,9.0876 16.0017,8.5018L15.4982,7.9983C14.9124,7.4125 14.9124,6.4627 15.4982,5.8769L18.8769,2.4982C19.4627,1.9124 20.4125,1.9124 20.9983,2.4982Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M13.8284,3l7.0711,7.0711l-2.8284,2.8284l-7.0711,-7.0711z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,27 @@
<!--
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 xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.5,11L16,7.5L16,4L8,4L8,7.5L11.5,11L11.5,13L8,16.5L8,20L16,20L16,16.5L12.5,13L12.5,11ZM6,2L18,2L18,8L17.99,8L18,8.01L14,12L18,16L17.99,16.01L18,16.01L18,22L6,22L6,16.01L6.01,16.01L6,16L10,12L6,8.01L6.01,8L6,8L6,2Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -1,24 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18.0,16.08c-0.76,0.0 -1.4,0.3 -1.9,0.77L8.91,12.7c0.05,-0.2 0.09,-0.4 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.5,0.5 1.2,0.81 2.0,0.81 1.66,0.0 3.0,-1.34 3.0,-3.0s-1.34,-3.0 -3.0,-3.0 -3.0,1.34 -3.0,3.0c0.0,0.2 0.0,0.4 0.0,0.7L8.04,9.81C7.5,9.31 6.79,9.0 6.0,9.0c-1.66,0.0 -3.0,1.34 -3.0,3.0s1.34,3.0 3.0,3.0c0.79,0.0 1.5,-0.31 2.04,-0.81l7.12,4.16c0.0,0.21 0.0,0.43 0.0,0.65 0.0,1.61 1.31,2.92 2.92,2.92 1.61,0.0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -1,7 +1,7 @@
<!--
Copyright (C) 2017 The Android Open Source Project
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
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
@@ -13,28 +13,7 @@ Copyright (C) 2017 The Android Open Source Project
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
android:fillAlpha="0.066"
android:fillColor="#000000"/>
<path
android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
android:fillColor="#283593"/>
<path
android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
android:fillColor="#1a237e"/>
<path
android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
android:fillColor="#5c6bc0"/>
<path
android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
android:fillColor="#3f51b5"/>
<path
android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
android:fillColor="#FFFFFF"/>
</vector>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/icon_bg"/>
<foreground android:drawable="@drawable/p"/>
</adaptive-icon>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 The Android Open Source Project
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
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
@@ -14,6 +14,5 @@ Copyright (C) 2017 The Android Open Source Project
See the License for the specific language governing permissions and
limitations under the License.
-->
<paths>
<external-path name="cats" path="Pictures/Cats" />
</paths>
<color xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#C5E1A5" />

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="left_ear" android:fillColor="#FF000000" android:pathData="M15.4,1l5.1000004,5.3l-6.3,2.8000002z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="left_ear_inside" android:fillColor="#FF000000" android:pathData="M15.4,1l3.5,6.2l-4.7,1.9z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="left_eye" android:fillColor="#FF000000" android:pathData="M20.5,11c0,1.7 -3,1.7 -3,0C17.5,9.3 20.5,9.3 20.5,11z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="leg2" android:fillColor="#FF000000" android:pathData="M16,37h5v6h-5z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="leg2_shadow" android:fillColor="#FF000000" android:pathData="M16,37h5v3h-5z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="leg3" android:fillColor="#FF000000" android:pathData="M27,37h5v6h-5z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="leg4" android:fillColor="#FF000000" android:pathData="M34,37h5v6h-5z"/>
</vector>

View File

@@ -1,27 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="mouth"
android:strokeColor="#FF000000"
android:strokeWidth="1.2"
android:strokeLineCap="round"
android:pathData="M29,14.3c-0.4,0.8 -1.3,1.4 -2.3,1.4c-1.4,0 -2.7,-1.3 -2.7,-2.7
M24,13c0,1.5 -1.2,2.7 -2.7,2.7c-1,0 -1.9,-0.5 -2.3,-1.4"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="nose" android:fillColor="#FF000000" android:pathData="M25.2,13c0,1.3 -2.3,1.3 -2.3,0S25.2,11.7 25.2,13z"/>
</vector>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:angle="-90"
android:startColor="#FF205090"
android:endColor="#FF001040"
android:type="linear"
/>
</shape>

View File

@@ -0,0 +1,33 @@
<!--
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 xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M49,65L54,65C60.075,65 65,60.075 65,54C65,47.925 60.075,43 54,43C47.925,43 43,47.925 43,54L43,108"
android:strokeWidth="16"
android:fillColor="#00000000"
android:strokeColor="#7CB342"
android:fillType="evenOdd"/>
<path
android:pathData="M51,65L54,65C60.075,65 65,60.075 65,54C65,47.925 60.075,43 54,43C47.925,43 43,47.925 43,54L43,108"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="right_ear" android:fillColor="#FF000000" android:pathData="M32.6,1l-5.0999985,5.3l6.299999,2.8000002z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="right_eye" android:fillColor="#FF000000" android:pathData="M30.5,11c0,1.7 -3,1.7 -3,0C27.5,9.3 30.5,9.3 30.5,11z"/>
</vector>

View File

@@ -1,30 +0,0 @@
<!--
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="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.5,2 2,6.5 2,12c0,5.5 4.5,10 10,10s10,-4.5 10,-10C22,6.5 17.5,2 12,2zM5.5,11c0,-1.6 3,-1.6 3,0C8.5,12.7 5.5,12.7 5.5,11zM17.5,14.6c-0.6,1 -1.7,1.7 -2.9,1.7c-1.1,0 -2,-0.6 -2.6,-1.4c-0.6,0.9 -1.6,1.4 -2.7,1.4c-1.3,0 -2.3,-0.7 -2.9,-1.8c-0.2,-0.3 0,-0.7 0.3,-0.8c0.3,-0.2 0.7,0 0.8,0.3c0.3,0.7 1,1.1 1.8,1.1c0.9,0 1.6,-0.5 1.9,-1.3c-0.2,-0.2 -0.4,-0.4 -0.4,-0.7c0,-1.3 2.3,-1.3 2.3,0c0,0.3 -0.2,0.6 -0.4,0.7c0.3,0.8 1.1,1.3 1.9,1.3c0.8,0 1.5,-0.6 1.8,-1.1c0.2,-0.3 0.6,-0.4 0.9,-0.2C17.6,13.9 17.7,14.3 17.5,14.6zM15.5,11c0,-1.6 3,-1.6 3,0C18.5,12.7 15.5,12.7 15.5,11z"/>
<path
android:fillColor="#FF000000"
android:pathData="M5.2,1.0l4.1000004,4.2l-5.0,2.1000004z"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.8,1.0l-4.0999994,4.2l5.000001,2.1000004z"/>
</vector>

View File

@@ -1,26 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="tail"
android:strokeColor="#FF000000"
android:strokeWidth="5"
android:strokeLineCap="round"
android:pathData="M35,35.5h5.9c2.1,0 3.8,-1.7 3.8,-3.8v-6.2"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="tail_cap" android:fillColor="#FF000000" android:pathData="M42.2,25.5c0,-1.4 1.1,-2.5 2.5,-2.5s2.5,1.1 2.5,2.5H42.2z"/>
</vector>

View File

@@ -1,22 +0,0 @@
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="tail_shadow" android:fillColor="#FF000000" android:pathData="M40,38l0,-5l-1,0l0,5z"/>
</vector>

View File

@@ -0,0 +1,19 @@
<?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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/toolbar_bg_color" />
</shape>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
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.
@@ -13,12 +14,12 @@
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="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/>
</vector>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@android:color/black" />
<corners android:radius="4dp" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,48 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res/com.android.egg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#666"
tools:context=".paint.PaintActivity"
android:id="@+id/contentView" >
<include layout="@layout/toolbar"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="top"
/>
<include layout="@layout/colors"
android:id="@+id/colors"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="top"
android:visibility="gone"
/>
<include layout="@layout/brushes"
android:id="@+id/brushes"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="top"
android:visibility="gone"
/>
</FrameLayout>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
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
@@ -13,10 +14,12 @@ Copyright (C) 2016 The Android Open Source Project
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path android:name="body" android:fillColor="#FF000000" android:pathData="M9,20h30v18h-30z"/>
</vector>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="left"
android:background="@drawable/toolbar_bg"
android:elevation="10dp"
>
</LinearLayout>

View File

@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:gravity="center_horizontal"
android:clipToPadding="false">
<FrameLayout
android:layout_width="96dp"
android:layout_height="wrap_content">
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="center"
android:scaleType="fitCenter" />
<LinearLayout
android:id="@+id/contextGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
android:layout_gravity="bottom">
<ImageView
android:id="@android:id/shareText"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/ic_share"
android:scaleType="fitCenter"
android:background="#40000000"/>
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@android:id/closeButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="4dp"
android:src="@drawable/ic_close"
android:scaleType="fitCenter"
android:background="#40000000"/>
</LinearLayout>
</FrameLayout>
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:gravity="center"/>
</LinearLayout>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
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
@@ -13,11 +14,13 @@ Copyright (C) 2016 The Android Open Source Project
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="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="left"
android:background="@drawable/toolbar_bg"
android:elevation="10dp"
>
<path android:name="right_ear_inside" android:fillColor="#FF000000" android:pathData="M33.8,9.1l-4.7,-1.9l3.5,-6.2z"/>
</vector>
</LinearLayout>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<EditText
android:id="@android:id/edit"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:paddingLeft="4dp" android:paddingRight="4dp"
android:paddingBottom="6dp" android:paddingTop="6dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:id="@+id/icon"
android:tint="?android:attr/colorControlNormal"/>
<TextView android:layout_width="64dp" android:layout_height="wrap_content"
android:gravity="top|center_horizontal"
android:id="@+id/text" />
</LinearLayout>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
</FrameLayout>

View File

@@ -0,0 +1,111 @@
<?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.
-->
<com.android.egg.paint.CutoutAvoidingToolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:gravity="left"
android:background="@drawable/toolbar_bg"
android:elevation="20dp"
>
<Space
android:tag="cutoutLeft"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
<LinearLayout
android:layout_width="0dp"
android:tag="beforeCutout"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_weight="1"
tools:ignore="UselessParent">
<ImageButton
android:id="@+id/btnBrush"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tint="@color/toolbar_icon_color"
android:background="@drawable/toolbar_button_bg"
/>
<ImageButton
android:id="@+id/btnColor"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tint="@color/toolbar_icon_color"
android:background="@drawable/toolbar_button_bg"
/>
<ImageButton
android:id="@+id/btnSample"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/toolbar_button_bg"
android:tint="@color/toolbar_icon_color"
android:src="@drawable/ic_dropper" />
</LinearLayout>
<Space
android:tag="cutoutCenter"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
<LinearLayout
android:layout_width="0dp"
android:tag="afterCutout"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_weight="1"
tools:ignore="UselessParent">
<ImageButton
android:id="@+id/btnZen"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/toolbar_button_bg"
android:tint="@color/toolbar_icon_color"
android:src="@drawable/ic_hourglass" />
<ImageButton
android:id="@+id/btnClear"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/toolbar_button_bg"
android:tint="@color/toolbar_icon_color"
android:src="@drawable/ic_clear" />
</LinearLayout>
<Space
android:tag="cutoutRight"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
</com.android.egg.paint.CutoutAvoidingToolbar>

View File

@@ -0,0 +1,21 @@
<?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>
<color name="toolbar_bg_color">#FF333333</color>
<color name="paper_color">#FF000000</color>
<color name="paint_color">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,21 @@
<!--
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="AppTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>

View File

@@ -0,0 +1,19 @@
<!--
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>
<declare-styleable name="ToolbarView">
</declare-styleable>
</resources>

View File

@@ -0,0 +1,21 @@
<?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>
<color name="toolbar_bg_color">#FFDDDDDD</color>
<color name="paper_color">#FFFFFFFF</color>
<color name="paint_color">#FF000000</color>
</resources>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 The Android Open Source Project
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.
@@ -15,40 +15,5 @@ Copyright (C) 2016 The Android Open Source Project
limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<string name="app_name" translatable="false">Android Easter Egg</string>
<string name="notification_name" translatable="false">Android Neko</string>
<string name="notification_channel_name" translatable="false">New cats</string>
<string name="default_tile_name" translatable="false">\????</string>
<string name="notification_title" translatable="false">A cat is here.</string>
<string name="default_cat_name" translatable="false">Cat #%s</string>
<string name="directory_name" translatable="false">Cats</string>
<string name="confirm_delete" translatable="false">Forget %s?</string>
<string-array name="food_names" translatable="false">
<item>Empty dish</item>
<item>Bits</item>
<item>Fish</item>
<item>Chicken</item>
<item>Treat</item>
</string-array>
<array name="food_icons">
<item>@drawable/food_dish</item>
<item>@drawable/food_bits</item>
<item>@drawable/food_sysuituna</item>
<item>@drawable/food_chicken</item>
<item>@drawable/food_cookie</item>
</array>
<integer-array name="food_intervals">
<item>0</item>
<item>15</item>
<item>30</item>
<item>60</item>
<item>120</item>
</integer-array>
<integer-array name="food_new_cat_prob">
<item>0</item>
<item>5</item>
<item>35</item>
<item>65</item>
<item>90</item>
</integer-array>
<string name="app_name" translatable="false">PAINT.APK</string>
</resources>

View File

@@ -0,0 +1,23 @@
<!--
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="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
</resources>

View File

@@ -1,434 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import java.io.ByteArrayOutputStream;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import com.android.egg.R;
import com.android.internal.logging.MetricsLogger;
import static com.android.egg.neko.NekoLand.CHAN_ID;
public class Cat extends Drawable {
public static final long[] PURR = {0, 40, 20, 40, 20, 40, 20, 40, 20, 40, 20, 40};
private Random mNotSoRandom;
private Bitmap mBitmap;
private long mSeed;
private String mName;
private int mBodyColor;
private int mFootType;
private boolean mBowTie;
private synchronized Random notSoRandom(long seed) {
if (mNotSoRandom == null) {
mNotSoRandom = new Random();
mNotSoRandom.setSeed(seed);
}
return mNotSoRandom;
}
public static final float frandrange(Random r, float a, float b) {
return (b-a)*r.nextFloat() + a;
}
public static final Object choose(Random r, Object...l) {
return l[r.nextInt(l.length)];
}
public static final int chooseP(Random r, int[] a) {
int pct = r.nextInt(1000);
final int stop = a.length-2;
int i=0;
while (i<stop) {
pct -= a[i];
if (pct < 0) break;
i+=2;
}
return a[i+1];
}
public static final int getColorIndex(int q, int[] a) {
for(int i = 1; i < a.length; i+=2) {
if (a[i] == q) {
return i/2;
}
}
return -1;
}
public static final int[] P_BODY_COLORS = {
180, 0xFF212121, // black
180, 0xFFFFFFFF, // white
140, 0xFF616161, // gray
140, 0xFF795548, // brown
100, 0xFF90A4AE, // steel
100, 0xFFFFF9C4, // buff
100, 0xFFFF8F00, // orange
5, 0xFF29B6F6, // blue..?
5, 0xFFFFCDD2, // pink!?
5, 0xFFCE93D8, // purple?!?!?
4, 0xFF43A047, // yeah, why not green
1, 0, // ?!?!?!
};
public static final int[] P_COLLAR_COLORS = {
250, 0xFFFFFFFF,
250, 0xFF000000,
250, 0xFFF44336,
50, 0xFF1976D2,
50, 0xFFFDD835,
50, 0xFFFB8C00,
50, 0xFFF48FB1,
50, 0xFF4CAF50,
};
public static final int[] P_BELLY_COLORS = {
750, 0,
250, 0xFFFFFFFF,
};
public static final int[] P_DARK_SPOT_COLORS = {
700, 0,
250, 0xFF212121,
50, 0xFF6D4C41,
};
public static final int[] P_LIGHT_SPOT_COLORS = {
700, 0,
300, 0xFFFFFFFF,
};
private CatParts D;
public static void tint(int color, Drawable ... ds) {
for (Drawable d : ds) {
if (d != null) {
d.mutate().setTint(color);
}
}
}
public static boolean isDark(int color) {
final int r = (color & 0xFF0000) >> 16;
final int g = (color & 0x00FF00) >> 8;
final int b = color & 0x0000FF;
return (r + g + b) < 0x80;
}
public Cat(Context context, long seed) {
D = new CatParts(context);
mSeed = seed;
setName(context.getString(R.string.default_cat_name,
String.valueOf(mSeed % 1000)));
final Random nsr = notSoRandom(seed);
// body color
mBodyColor = chooseP(nsr, P_BODY_COLORS);
if (mBodyColor == 0) mBodyColor = Color.HSVToColor(new float[] {
nsr.nextFloat()*360f, frandrange(nsr,0.5f,1f), frandrange(nsr,0.5f, 1f)});
tint(mBodyColor, D.body, D.head, D.leg1, D.leg2, D.leg3, D.leg4, D.tail,
D.leftEar, D.rightEar, D.foot1, D.foot2, D.foot3, D.foot4, D.tailCap);
tint(0x20000000, D.leg2Shadow, D.tailShadow);
if (isDark(mBodyColor)) {
tint(0xFFFFFFFF, D.leftEye, D.rightEye, D.mouth, D.nose);
}
tint(isDark(mBodyColor) ? 0xFFEF9A9A : 0x20D50000, D.leftEarInside, D.rightEarInside);
tint(chooseP(nsr, P_BELLY_COLORS), D.belly);
tint(chooseP(nsr, P_BELLY_COLORS), D.back);
final int faceColor = chooseP(nsr, P_BELLY_COLORS);
tint(faceColor, D.faceSpot);
if (!isDark(faceColor)) {
tint(0xFF000000, D.mouth, D.nose);
}
mFootType = 0;
if (nsr.nextFloat() < 0.25f) {
mFootType = 4;
tint(0xFFFFFFFF, D.foot1, D.foot2, D.foot3, D.foot4);
} else {
if (nsr.nextFloat() < 0.25f) {
mFootType = 2;
tint(0xFFFFFFFF, D.foot1, D.foot3);
} else if (nsr.nextFloat() < 0.25f) {
mFootType = 3; // maybe -2 would be better? meh.
tint(0xFFFFFFFF, D.foot2, D.foot4);
} else if (nsr.nextFloat() < 0.1f) {
mFootType = 1;
tint(0xFFFFFFFF, (Drawable) choose(nsr, D.foot1, D.foot2, D.foot3, D.foot4));
}
}
tint(nsr.nextFloat() < 0.333f ? 0xFFFFFFFF : mBodyColor, D.tailCap);
final int capColor = chooseP(nsr, isDark(mBodyColor) ? P_LIGHT_SPOT_COLORS : P_DARK_SPOT_COLORS);
tint(capColor, D.cap);
//tint(chooseP(nsr, isDark(bodyColor) ? P_LIGHT_SPOT_COLORS : P_DARK_SPOT_COLORS), D.nose);
final int collarColor = chooseP(nsr, P_COLLAR_COLORS);
tint(collarColor, D.collar);
mBowTie = nsr.nextFloat() < 0.1f;
tint(mBowTie ? collarColor : 0, D.bowtie);
}
public static Cat create(Context context) {
return new Cat(context, Math.abs(ThreadLocalRandom.current().nextInt()));
}
public Notification.Builder buildNotification(Context context) {
final Bundle extras = new Bundle();
extras.putString("android.substName", context.getString(R.string.notification_name));
final Intent intent = new Intent(Intent.ACTION_MAIN)
.setClass(context, NekoLand.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return new Notification.Builder(context)
.setSmallIcon(Icon.createWithResource(context, R.drawable.stat_icon))
.setLargeIcon(createNotificationLargeIcon(context))
.setColor(getBodyColor())
.setPriority(Notification.PRIORITY_LOW)
.setContentTitle(context.getString(R.string.notification_title))
.setShowWhen(true)
.setCategory(Notification.CATEGORY_STATUS)
.setContentText(getName())
.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0))
.setAutoCancel(true)
.setChannel(CHAN_ID)
.setVibrate(PURR)
.addExtras(extras);
}
public long getSeed() {
return mSeed;
}
@Override
public void draw(Canvas canvas) {
final int w = Math.min(canvas.getWidth(), canvas.getHeight());
final int h = w;
if (mBitmap == null || mBitmap.getWidth() != w || mBitmap.getHeight() != h) {
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
final Canvas bitCanvas = new Canvas(mBitmap);
slowDraw(bitCanvas, 0, 0, w, h);
}
canvas.drawBitmap(mBitmap, 0, 0, null);
}
private void slowDraw(Canvas canvas, int x, int y, int w, int h) {
for (int i = 0; i < D.drawingOrder.length; i++) {
final Drawable d = D.drawingOrder[i];
if (d != null) {
d.setBounds(x, y, x+w, y+h);
d.draw(canvas);
}
}
}
public Bitmap createBitmap(int w, int h) {
if (mBitmap != null && mBitmap.getWidth() == w && mBitmap.getHeight() == h) {
return mBitmap.copy(mBitmap.getConfig(), true);
}
Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
slowDraw(new Canvas(result), 0, 0, w, h);
return result;
}
public static Icon recompressIcon(Icon bitmapIcon) {
if (bitmapIcon.getType() != Icon.TYPE_BITMAP) return bitmapIcon;
final Bitmap bits = bitmapIcon.getBitmap();
final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
bits.getWidth() * bits.getHeight() * 2); // guess 50% compression
final boolean ok = bits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
if (!ok) return null;
return Icon.createWithData(ostream.toByteArray(), 0, ostream.size());
}
public Icon createNotificationLargeIcon(Context context) {
final Resources res = context.getResources();
final int w = 2*res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
final int h = 2*res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
return recompressIcon(createIcon(context, w, h));
}
public Icon createIcon(Context context, int w, int h) {
Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(result);
final Paint pt = new Paint();
float[] hsv = new float[3];
Color.colorToHSV(mBodyColor, hsv);
hsv[2] = (hsv[2]>0.5f)
? (hsv[2] - 0.25f)
: (hsv[2] + 0.25f);
pt.setColor(Color.HSVToColor(hsv));
float r = w/2;
canvas.drawCircle(r, r, r, pt);
int m = w/10;
slowDraw(canvas, m, m, w-m-m, h-m-m);
return Icon.createWithBitmap(result);
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public int getBodyColor() {
return mBodyColor;
}
public void logAdd(Context context) {
logCatAction(context, "egg_neko_add");
}
public void logRename(Context context) {
logCatAction(context, "egg_neko_rename");
}
public void logRemove(Context context) {
logCatAction(context, "egg_neko_remove");
}
public void logShare(Context context) {
logCatAction(context, "egg_neko_share");
}
private void logCatAction(Context context, String prefix) {
MetricsLogger.count(context, prefix, 1);
MetricsLogger.histogram(context, prefix +"_color",
getColorIndex(mBodyColor, P_BODY_COLORS));
MetricsLogger.histogram(context, prefix + "_bowtie", mBowTie ? 1 : 0);
MetricsLogger.histogram(context, prefix + "_feet", mFootType);
}
public static class CatParts {
public Drawable leftEar;
public Drawable rightEar;
public Drawable rightEarInside;
public Drawable leftEarInside;
public Drawable head;
public Drawable faceSpot;
public Drawable cap;
public Drawable mouth;
public Drawable body;
public Drawable foot1;
public Drawable leg1;
public Drawable foot2;
public Drawable leg2;
public Drawable foot3;
public Drawable leg3;
public Drawable foot4;
public Drawable leg4;
public Drawable tail;
public Drawable leg2Shadow;
public Drawable tailShadow;
public Drawable tailCap;
public Drawable belly;
public Drawable back;
public Drawable rightEye;
public Drawable leftEye;
public Drawable nose;
public Drawable bowtie;
public Drawable collar;
public Drawable[] drawingOrder;
public CatParts(Context context) {
body = context.getDrawable(R.drawable.body);
head = context.getDrawable(R.drawable.head);
leg1 = context.getDrawable(R.drawable.leg1);
leg2 = context.getDrawable(R.drawable.leg2);
leg3 = context.getDrawable(R.drawable.leg3);
leg4 = context.getDrawable(R.drawable.leg4);
tail = context.getDrawable(R.drawable.tail);
leftEar = context.getDrawable(R.drawable.left_ear);
rightEar = context.getDrawable(R.drawable.right_ear);
rightEarInside = context.getDrawable(R.drawable.right_ear_inside);
leftEarInside = context.getDrawable(R.drawable.left_ear_inside);
faceSpot = context.getDrawable(R.drawable.face_spot);
cap = context.getDrawable(R.drawable.cap);
mouth = context.getDrawable(R.drawable.mouth);
foot4 = context.getDrawable(R.drawable.foot4);
foot3 = context.getDrawable(R.drawable.foot3);
foot1 = context.getDrawable(R.drawable.foot1);
foot2 = context.getDrawable(R.drawable.foot2);
leg2Shadow = context.getDrawable(R.drawable.leg2_shadow);
tailShadow = context.getDrawable(R.drawable.tail_shadow);
tailCap = context.getDrawable(R.drawable.tail_cap);
belly = context.getDrawable(R.drawable.belly);
back = context.getDrawable(R.drawable.back);
rightEye = context.getDrawable(R.drawable.right_eye);
leftEye = context.getDrawable(R.drawable.left_eye);
nose = context.getDrawable(R.drawable.nose);
collar = context.getDrawable(R.drawable.collar);
bowtie = context.getDrawable(R.drawable.bowtie);
drawingOrder = getDrawingOrder();
}
private Drawable[] getDrawingOrder() {
return new Drawable[] {
collar,
leftEar, leftEarInside, rightEar, rightEarInside,
head,
faceSpot,
cap,
leftEye, rightEye,
nose, mouth,
tail, tailCap, tailShadow,
foot1, leg1,
foot2, leg2,
foot3, leg3,
foot4, leg4,
leg2Shadow,
body, belly,
bowtie
};
}
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import com.android.egg.R;
public class Food {
private final int mType;
private static int[] sIcons;
private static String[] sNames;
public Food(int type) {
mType = type;
}
public Icon getIcon(Context context) {
if (sIcons == null) {
TypedArray icons = context.getResources().obtainTypedArray(R.array.food_icons);
sIcons = new int[icons.length()];
for (int i = 0; i < sIcons.length; i++) {
sIcons[i] = icons.getResourceId(i, 0);
}
icons.recycle();
}
return Icon.createWithResource(context, sIcons[mType]);
}
public String getName(Context context) {
if (sNames == null) {
sNames = context.getResources().getStringArray(R.array.food_names);
}
return sNames[mType];
}
public long getInterval(Context context) {
return context.getResources().getIntArray(R.array.food_intervals)[mType];
}
public int getType() {
return mType;
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.app.Activity;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.util.Log;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
public class NekoActivationActivity extends Activity {
private void toastUp(String s) {
Toast toast = Toast.makeText(this, s, Toast.LENGTH_SHORT);
toast.getView().setBackgroundDrawable(null);
toast.show();
}
@Override
public void onStart() {
super.onStart();
final PackageManager pm = getPackageManager();
final ComponentName cn = new ComponentName(this, NekoTile.class);
if (pm.getComponentEnabledSetting(cn) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
if (NekoLand.DEBUG) {
Log.v("Neko", "Disabling tile.");
}
pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
MetricsLogger.histogram(this, "egg_neko_enable", 0);
toastUp("\uD83D\uDEAB");
} else {
if (NekoLand.DEBUG) {
Log.v("Neko", "Enabling tile.");
}
pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
MetricsLogger.histogram(this, "egg_neko_enable", 1);
toastUp("\uD83D\uDC31");
}
finish();
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.support.annotation.NonNull;
import android.app.Dialog;
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.egg.R;
import com.android.internal.logging.MetricsLogger;
import java.util.ArrayList;
public class NekoDialog extends Dialog {
private final Adapter mAdapter;
public NekoDialog(@NonNull Context context) {
super(context, android.R.style.Theme_Material_Dialog_NoActionBar);
RecyclerView view = new RecyclerView(getContext());
mAdapter = new Adapter(getContext());
view.setLayoutManager(new GridLayoutManager(getContext(), 2));
view.setAdapter(mAdapter);
final float dp = context.getResources().getDisplayMetrics().density;
final int pad = (int)(16*dp);
view.setPadding(pad, pad, pad, pad);
setContentView(view);
}
private void onFoodSelected(Food food) {
PrefState prefs = new PrefState(getContext());
int currentState = prefs.getFoodState();
if (currentState == 0 && food.getType() != 0) {
NekoService.registerJob(getContext(), food.getInterval(getContext()));
}
MetricsLogger.histogram(getContext(), "egg_neko_offered_food", food.getType());
prefs.setFoodState(food.getType());
dismiss();
}
private class Adapter extends RecyclerView.Adapter<Holder> {
private final Context mContext;
private final ArrayList<Food> mFoods = new ArrayList<>();
public Adapter(Context context) {
mContext = context;
int[] foods = context.getResources().getIntArray(R.array.food_names);
// skip food 0, you can't choose it
for (int i=1; i<foods.length; i++) {
mFoods.add(new Food(i));
}
}
@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
return new Holder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.food_layout, parent, false));
}
@Override
public void onBindViewHolder(final Holder holder, int position) {
final Food food = mFoods.get(position);
((ImageView) holder.itemView.findViewById(R.id.icon))
.setImageIcon(food.getIcon(mContext));
((TextView) holder.itemView.findViewById(R.id.text))
.setText(food.getName(mContext));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onFoodSelected(mFoods.get(holder.getAdapterPosition()));
}
});
}
@Override
public int getItemCount() {
return mFoods.size();
}
}
public static class Holder extends RecyclerView.ViewHolder {
public Holder(View itemView) {
super(itemView);
}
}
}

View File

@@ -1,338 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.Manifest;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore.Images;
import android.support.v4.content.FileProvider;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.egg.R;
import com.android.egg.neko.PrefState.PrefsListener;
import com.android.internal.logging.MetricsLogger;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class NekoLand extends Activity implements PrefsListener {
public static String CHAN_ID = "EGG";
public static boolean DEBUG = false;
public static boolean DEBUG_NOTIFICATIONS = false;
private static final int EXPORT_BITMAP_SIZE = 600;
private static final int STORAGE_PERM_REQUEST = 123;
private static boolean CAT_GEN = false;
private PrefState mPrefs;
private CatAdapter mAdapter;
private Cat mPendingShareCat;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.neko_activity);
final ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setLogo(Cat.create(this));
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayShowHomeEnabled(true);
}
mPrefs = new PrefState(this);
mPrefs.setListener(this);
final RecyclerView recyclerView = findViewById(R.id.holder);
mAdapter = new CatAdapter();
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
int numCats = updateCats();
MetricsLogger.histogram(this, "egg_neko_visit_gallery", numCats);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPrefs.setListener(null);
}
private int updateCats() {
Cat[] cats;
if (CAT_GEN) {
cats = new Cat[50];
for (int i = 0; i < cats.length; i++) {
cats[i] = Cat.create(this);
}
} else {
final float[] hsv = new float[3];
List<Cat> list = mPrefs.getCats();
Collections.sort(list, new Comparator<Cat>() {
@Override
public int compare(Cat cat, Cat cat2) {
Color.colorToHSV(cat.getBodyColor(), hsv);
float bodyH1 = hsv[0];
Color.colorToHSV(cat2.getBodyColor(), hsv);
float bodyH2 = hsv[0];
return Float.compare(bodyH1, bodyH2);
}
});
cats = list.toArray(new Cat[0]);
}
mAdapter.setCats(cats);
return cats.length;
}
private void onCatClick(Cat cat) {
if (CAT_GEN) {
mPrefs.addCat(cat);
new AlertDialog.Builder(NekoLand.this)
.setTitle("Cat added")
.setPositiveButton(android.R.string.ok, null)
.show();
} else {
showNameDialog(cat);
}
// noman.notify(1, cat.buildNotification(NekoLand.this).build());
}
private void onCatRemove(Cat cat) {
cat.logRemove(this);
mPrefs.removeCat(cat);
}
private void showNameDialog(final Cat cat) {
final Context context = new ContextThemeWrapper(this,
android.R.style.Theme_Material_Light_Dialog_NoActionBar);
// TODO: Move to XML, add correct margins.
View view = LayoutInflater.from(context).inflate(R.layout.edit_text, null);
final EditText text = (EditText) view.findViewById(android.R.id.edit);
text.setText(cat.getName());
text.setSelection(cat.getName().length());
final int size = context.getResources()
.getDimensionPixelSize(android.R.dimen.app_icon_size);
Drawable catIcon = cat.createIcon(this, size, size).loadDrawable(this);
new AlertDialog.Builder(context)
.setTitle(" ")
.setIcon(catIcon)
.setView(view)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cat.logRename(context);
cat.setName(text.getText().toString().trim());
mPrefs.addCat(cat);
}
}).show();
}
@Override
public void onPrefsChanged() {
updateCats();
}
private class CatAdapter extends RecyclerView.Adapter<CatHolder> {
private Cat[] mCats;
public void setCats(Cat[] cats) {
mCats = cats;
notifyDataSetChanged();
}
@Override
public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new CatHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.cat_view, parent, false));
}
private void setContextGroupVisible(final CatHolder holder, boolean vis) {
final View group = holder.contextGroup;
if (vis && group.getVisibility() != View.VISIBLE) {
group.setAlpha(0);
group.setVisibility(View.VISIBLE);
group.animate().alpha(1.0f).setDuration(333);
Runnable hideAction = new Runnable() {
@Override
public void run() {
setContextGroupVisible(holder, false);
}
};
group.setTag(hideAction);
group.postDelayed(hideAction, 5000);
} else if (!vis && group.getVisibility() == View.VISIBLE) {
group.removeCallbacks((Runnable) group.getTag());
group.animate().alpha(0f).setDuration(250).withEndAction(new Runnable() {
@Override
public void run() {
group.setVisibility(View.INVISIBLE);
}
});
}
}
@Override
public void onBindViewHolder(final CatHolder holder, int position) {
Context context = holder.itemView.getContext();
final int size = context.getResources().getDimensionPixelSize(R.dimen.neko_display_size);
holder.imageView.setImageIcon(mCats[position].createIcon(context, size, size));
holder.textView.setText(mCats[position].getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onCatClick(mCats[holder.getAdapterPosition()]);
}
});
holder.itemView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
setContextGroupVisible(holder, true);
return true;
}
});
holder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setContextGroupVisible(holder, false);
new AlertDialog.Builder(NekoLand.this)
.setTitle(getString(R.string.confirm_delete, mCats[position].getName()))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onCatRemove(mCats[holder.getAdapterPosition()]);
}
})
.show();
}
});
holder.share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setContextGroupVisible(holder, false);
Cat cat = mCats[holder.getAdapterPosition()];
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
mPendingShareCat = cat;
requestPermissions(
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
STORAGE_PERM_REQUEST);
return;
}
shareCat(cat);
}
});
}
@Override
public int getItemCount() {
return mCats.length;
}
}
private void shareCat(Cat cat) {
final File dir = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
getString(R.string.directory_name));
if (!dir.exists() && !dir.mkdirs()) {
Log.e("NekoLand", "save: error: can't create Pictures directory");
return;
}
final File png = new File(dir, cat.getName().replaceAll("[/ #:]+", "_") + ".png");
Bitmap bitmap = cat.createBitmap(EXPORT_BITMAP_SIZE, EXPORT_BITMAP_SIZE);
if (bitmap != null) {
try {
OutputStream os = new FileOutputStream(png);
bitmap.compress(Bitmap.CompressFormat.PNG, 0, os);
os.close();
MediaScannerConnection.scanFile(
this,
new String[] {png.toString()},
new String[] {"image/png"},
null);
Log.v("Neko", "cat file: " + png);
Uri uri = FileProvider.getUriForFile(this, "com.android.egg.fileprovider", png);
Log.v("Neko", "cat uri: " + uri);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.putExtra(Intent.EXTRA_SUBJECT, cat.getName());
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setType("image/png");
startActivity(Intent.createChooser(intent, null));
cat.logShare(this);
} catch (IOException e) {
Log.e("NekoLand", "save: error: " + e);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
if (requestCode == STORAGE_PERM_REQUEST) {
if (mPendingShareCat != null) {
shareCat(mPendingShareCat);
mPendingShareCat = null;
}
}
}
private static class CatHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final TextView textView;
private final View contextGroup;
private final View delete;
private final View share;
public CatHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(android.R.id.icon);
textView = (TextView) itemView.findViewById(android.R.id.title);
contextGroup = itemView.findViewById(R.id.contextGroup);
delete = itemView.findViewById(android.R.id.closeButton);
share = itemView.findViewById(android.R.id.shareText);
}
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.support.annotation.Nullable;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.view.WindowManager;
public class NekoLockedActivity extends Activity implements OnDismissListener {
private NekoDialog mDialog;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mDialog = new NekoDialog(this);
mDialog.setOnDismissListener(this);
mDialog.show();
}
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
}

View File

@@ -1,165 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import java.util.List;
import android.util.Log;
import com.android.egg.R;
import java.util.Random;
import static com.android.egg.neko.Cat.PURR;
import static com.android.egg.neko.NekoLand.CHAN_ID;
public class NekoService extends JobService {
private static final String TAG = "NekoService";
public static int JOB_ID = 42;
public static int CAT_NOTIFICATION = 1;
public static int DEBUG_NOTIFICATION = 1234;
public static float CAT_CAPTURE_PROB = 1.0f; // generous
public static long SECONDS = 1000;
public static long MINUTES = 60 * SECONDS;
public static long INTERVAL_FLEX = 5 * MINUTES;
public static float INTERVAL_JITTER_FRAC = 0.25f;
private static void setupNotificationChannels(Context context) {
NotificationManager noman = context.getSystemService(NotificationManager.class);
NotificationChannel eggChan = new NotificationChannel(CHAN_ID,
context.getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT);
eggChan.setSound(Uri.EMPTY, Notification.AUDIO_ATTRIBUTES_DEFAULT); // cats are quiet
eggChan.setVibrationPattern(PURR); // not totally quiet though
eggChan.setBlockableSystem(true); // unlike a real cat, you can push this one off your lap
eggChan.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); // cats sit in the window
noman.createNotificationChannel(eggChan);
}
@Override
public boolean onStartJob(JobParameters params) {
Log.v(TAG, "Starting job: " + String.valueOf(params));
NotificationManager noman = getSystemService(NotificationManager.class);
if (NekoLand.DEBUG_NOTIFICATIONS) {
final Bundle extras = new Bundle();
extras.putString("android.substName", getString(R.string.notification_name));
final int size = getResources()
.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
final Cat cat = Cat.create(this);
final Notification.Builder builder
= cat.buildNotification(this)
.setContentTitle("DEBUG")
.setChannel(NekoLand.CHAN_ID)
.setContentText("Ran job: " + params);
noman.notify(DEBUG_NOTIFICATION, builder.build());
}
final PrefState prefs = new PrefState(this);
int food = prefs.getFoodState();
if (food != 0) {
prefs.setFoodState(0); // nom
final Random rng = new Random();
if (rng.nextFloat() <= CAT_CAPTURE_PROB) {
Cat cat;
List<Cat> cats = prefs.getCats();
final int[] probs = getResources().getIntArray(R.array.food_new_cat_prob);
final float new_cat_prob = (float)((food < probs.length) ? probs[food] : 50) / 100f;
if (cats.size() == 0 || rng.nextFloat() <= new_cat_prob) {
cat = Cat.create(this);
prefs.addCat(cat);
cat.logAdd(this);
Log.v(TAG, "A new cat is here: " + cat.getName());
} else {
cat = cats.get(rng.nextInt(cats.size()));
Log.v(TAG, "A cat has returned: " + cat.getName());
}
final Notification.Builder builder = cat.buildNotification(this);
noman.notify(CAT_NOTIFICATION, builder.build());
}
}
cancelJob(this);
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
public static void registerJobIfNeeded(Context context, long intervalMinutes) {
JobScheduler jss = context.getSystemService(JobScheduler.class);
JobInfo info = jss.getPendingJob(JOB_ID);
if (info == null) {
registerJob(context, intervalMinutes);
}
}
public static void registerJob(Context context, long intervalMinutes) {
setupNotificationChannels(context);
JobScheduler jss = context.getSystemService(JobScheduler.class);
jss.cancel(JOB_ID);
long interval = intervalMinutes * MINUTES;
long jitter = (long)(INTERVAL_JITTER_FRAC * interval);
interval += (long)(Math.random() * (2 * jitter)) - jitter;
final JobInfo jobInfo = new JobInfo.Builder(JOB_ID,
new ComponentName(context, NekoService.class))
.setPeriodic(interval, INTERVAL_FLEX)
.build();
Log.v(TAG, "A cat will visit in " + interval + "ms: " + String.valueOf(jobInfo));
jss.schedule(jobInfo);
if (NekoLand.DEBUG_NOTIFICATIONS) {
NotificationManager noman = context.getSystemService(NotificationManager.class);
noman.notify(DEBUG_NOTIFICATION, new Notification.Builder(context)
.setSmallIcon(R.drawable.stat_icon)
.setContentTitle(String.format("Job scheduled in %d min", (interval / MINUTES)))
.setContentText(String.valueOf(jobInfo))
.setPriority(Notification.PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.setChannel(NekoLand.CHAN_ID)
.setShowWhen(true)
.build());
}
}
public static void cancelJob(Context context) {
JobScheduler jss = context.getSystemService(JobScheduler.class);
Log.v(TAG, "Canceling job");
jss.cancel(JOB_ID);
}
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.content.Intent;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.Log;
import com.android.egg.neko.PrefState.PrefsListener;
import com.android.internal.logging.MetricsLogger;
public class NekoTile extends TileService implements PrefsListener {
private static final String TAG = "NekoTile";
private PrefState mPrefs;
@Override
public void onCreate() {
super.onCreate();
mPrefs = new PrefState(this);
}
@Override
public void onStartListening() {
super.onStartListening();
mPrefs.setListener(this);
updateState();
}
@Override
public void onStopListening() {
super.onStopListening();
mPrefs.setListener(null);
}
@Override
public void onTileAdded() {
super.onTileAdded();
MetricsLogger.count(this, "egg_neko_tile_added", 1);
}
@Override
public void onTileRemoved() {
super.onTileRemoved();
MetricsLogger.count(this, "egg_neko_tile_removed", 1);
}
@Override
public void onPrefsChanged() {
updateState();
}
private void updateState() {
Tile tile = getQsTile();
int foodState = mPrefs.getFoodState();
Food food = new Food(foodState);
if (foodState != 0) {
NekoService.registerJobIfNeeded(this, food.getInterval(this));
}
tile.setIcon(food.getIcon(this));
tile.setLabel(food.getName(this));
tile.setState(foodState != 0 ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
tile.updateTile();
}
@Override
public void onClick() {
if (mPrefs.getFoodState() != 0) {
// there's already food loaded, let's empty it
MetricsLogger.count(this, "egg_neko_empty_food", 1);
mPrefs.setFoodState(0);
NekoService.cancelJob(this);
} else {
// time to feed the cats
if (isLocked()) {
if (isSecure()) {
Log.d(TAG, "startActivityAndCollapse");
Intent intent = new Intent(this, NekoLockedActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityAndCollapse(intent);
} else {
unlockAndRun(new Runnable() {
@Override
public void run() {
showNekoDialog();
}
});
}
} else {
showNekoDialog();
}
}
}
private void showNekoDialog() {
Log.d(TAG, "showNekoDialog");
MetricsLogger.count(this, "egg_neko_select_food", 1);
showDialog(new NekoDialog(this));
}
}

View File

@@ -1,92 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.egg.neko;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PrefState implements OnSharedPreferenceChangeListener {
private static final String FILE_NAME = "mPrefs";
private static final String FOOD_STATE = "food";
private static final String CAT_KEY_PREFIX = "cat:";
private final Context mContext;
private final SharedPreferences mPrefs;
private PrefsListener mListener;
public PrefState(Context context) {
mContext = context;
mPrefs = mContext.getSharedPreferences(FILE_NAME, 0);
}
// Can also be used for renaming.
public void addCat(Cat cat) {
mPrefs.edit()
.putString(CAT_KEY_PREFIX + String.valueOf(cat.getSeed()), cat.getName())
.apply();
}
public void removeCat(Cat cat) {
mPrefs.edit().remove(CAT_KEY_PREFIX + String.valueOf(cat.getSeed())).apply();
}
public List<Cat> getCats() {
ArrayList<Cat> cats = new ArrayList<>();
Map<String, ?> map = mPrefs.getAll();
for (String key : map.keySet()) {
if (key.startsWith(CAT_KEY_PREFIX)) {
long seed = Long.parseLong(key.substring(CAT_KEY_PREFIX.length()));
Cat cat = new Cat(mContext, seed);
cat.setName(String.valueOf(map.get(key)));
cats.add(cat);
}
}
return cats;
}
public int getFoodState() {
return mPrefs.getInt(FOOD_STATE, 0);
}
public void setFoodState(int foodState) {
mPrefs.edit().putInt(FOOD_STATE, foodState).apply();
}
public void setListener(PrefsListener listener) {
mListener = listener;
if (mListener != null) {
mPrefs.registerOnSharedPreferenceChangeListener(this);
} else {
mPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
mListener.onPrefsChanged();
}
public interface PrefsListener {
void onPrefsChanged();
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright (C) 2017 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.egg.octo;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.egg.R;
public class Ocquarium extends Activity {
ImageView mImageView;
private OctopusDrawable mOcto;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final float dp = getResources().getDisplayMetrics().density;
getWindow().setBackgroundDrawableResource(R.drawable.octo_bg);
FrameLayout bg = new FrameLayout(this);
setContentView(bg);
bg.setAlpha(0f);
bg.animate().setStartDelay(500).setDuration(5000).alpha(1f).start();
mImageView = new ImageView(this);
bg.addView(mImageView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mOcto = new OctopusDrawable(getApplicationContext());
mOcto.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
mImageView.setImageDrawable(mOcto);
mImageView.setOnTouchListener(new View.OnTouchListener() {
boolean touching;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (mOcto.hitTest(motionEvent.getX(), motionEvent.getY())) {
touching = true;
mOcto.stopDrift();
}
break;
case MotionEvent.ACTION_MOVE:
if (touching) {
mOcto.moveTo(motionEvent.getX(), motionEvent.getY());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
touching = false;
mOcto.startDrift();
break;
}
return true;
}
});
}
@Override
protected void onPause() {
mOcto.stopDrift();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
mOcto.startDrift();
}
}

View File

@@ -1,436 +0,0 @@
/*
* Copyright (C) 2017 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.egg.octo;
import android.animation.TimeAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.animation.DynamicAnimation;
import android.support.animation.SpringForce;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.animation.SpringAnimation;
import android.support.animation.FloatValueHolder;
public class OctopusDrawable extends Drawable {
private static float BASE_SCALE = 100f;
public static boolean PATH_DEBUG = false;
private static int BODY_COLOR = 0xFF101010;
private static int ARM_COLOR = 0xFF101010;
private static int ARM_COLOR_BACK = 0xFF000000;
private static int EYE_COLOR = 0xFF808080;
private static int[] BACK_ARMS = {1, 3, 4, 6};
private static int[] FRONT_ARMS = {0, 2, 5, 7};
private Paint mPaint = new Paint();
private Arm[] mArms = new Arm[8];
final PointF point = new PointF();
private int mSizePx = 100;
final Matrix M = new Matrix();
final Matrix M_inv = new Matrix();
private TimeAnimator mDriftAnimation;
private boolean mBlinking;
private float[] ptmp = new float[2];
private float[] scaledBounds = new float[2];
public static float randfrange(float a, float b) {
return (float) (Math.random()*(b-a) + a);
}
public static float clamp(float v, float a, float b) {
return v<a?a:v>b?b:v;
}
public OctopusDrawable(Context context) {
float dp = context.getResources().getDisplayMetrics().density;
setSizePx((int) (100*dp));
mPaint.setAntiAlias(true);
for (int i=0; i<mArms.length; i++) {
final float bias = (float)i/(mArms.length-1) - 0.5f;
mArms[i] = new Arm(
0,0, // arm will be repositioned on moveTo
10f*bias + randfrange(0,20f), randfrange(20f,50f),
40f*bias+randfrange(-60f,60f), randfrange(30f, 80f),
randfrange(-40f,40f), randfrange(-80f,40f),
14f, 2f);
}
}
public void setSizePx(int size) {
mSizePx = size;
M.setScale(mSizePx/BASE_SCALE, mSizePx/BASE_SCALE);
// TaperedPathStroke.setMinStep(20f*BASE_SCALE/mSizePx); // nice little floaty circles
TaperedPathStroke.setMinStep(8f*BASE_SCALE/mSizePx); // classic tentacles
M.invert(M_inv);
}
public void startDrift() {
if (mDriftAnimation == null) {
mDriftAnimation = new TimeAnimator();
mDriftAnimation.setTimeListener(new TimeAnimator.TimeListener() {
float MAX_VY = 35f;
float JUMP_VY = -100f;
float MAX_VX = 15f;
private float ax = 0f, ay = 30f;
private float vx, vy;
long nextjump = 0;
long unblink = 0;
@Override
public void onTimeUpdate(TimeAnimator timeAnimator, long t, long dt) {
float t_sec = 0.001f * t;
float dt_sec = 0.001f * dt;
if (t > nextjump) {
vy = JUMP_VY;
nextjump = t + (long) randfrange(5000, 10000);
}
if (unblink > 0 && t > unblink) {
setBlinking(false);
unblink = 0;
} else if (Math.random() < 0.001f) {
setBlinking(true);
unblink = t + 200;
}
ax = (float) (MAX_VX * Math.sin(t_sec*.25f));
vx = clamp(vx + dt_sec * ax, -MAX_VX, MAX_VX);
vy = clamp(vy + dt_sec * ay, -100*MAX_VY, MAX_VY);
// oob check
if (point.y - BASE_SCALE/2 > scaledBounds[1]) {
vy = JUMP_VY;
} else if (point.y + BASE_SCALE < 0) {
vy = MAX_VY;
}
point.x = clamp(point.x + dt_sec * vx, 0, scaledBounds[0]);
point.y = point.y + dt_sec * vy;
repositionArms();
}
});
}
mDriftAnimation.start();
}
public void stopDrift() {
mDriftAnimation.cancel();
}
@Override
public void onBoundsChange(Rect bounds) {
final float w = bounds.width();
final float h = bounds.height();
lockArms(true);
moveTo(w/2, h/2);
lockArms(false);
scaledBounds[0] = w;
scaledBounds[1] = h;
M_inv.mapPoints(scaledBounds);
}
// real pixel coordinates
public void moveTo(float x, float y) {
point.x = x;
point.y = y;
mapPointF(M_inv, point);
repositionArms();
}
public boolean hitTest(float x, float y) {
ptmp[0] = x;
ptmp[1] = y;
M_inv.mapPoints(ptmp);
return Math.hypot(ptmp[0] - point.x, ptmp[1] - point.y) < BASE_SCALE/2;
}
private void lockArms(boolean l) {
for (Arm arm : mArms) {
arm.setLocked(l);
}
}
private void repositionArms() {
for (int i=0; i<mArms.length; i++) {
final float bias = (float)i/(mArms.length-1) - 0.5f;
mArms[i].setAnchor(
point.x+bias*30f,point.y+26f);
}
invalidateSelf();
}
private void drawPupil(Canvas canvas, float x, float y, float size, boolean open,
Paint pt) {
final float r = open ? size*.33f : size * .1f;
canvas.drawRoundRect(x - size, y - r, x + size, y + r, r, r, pt);
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.save();
{
canvas.concat(M);
// arms behind
mPaint.setColor(ARM_COLOR_BACK);
for (int i : BACK_ARMS) {
mArms[i].draw(canvas, mPaint);
}
// head/body/thing
mPaint.setColor(EYE_COLOR);
canvas.drawCircle(point.x, point.y, 36f, mPaint);
mPaint.setColor(BODY_COLOR);
canvas.save();
{
canvas.clipOutRect(point.x - 61f, point.y + 8f,
point.x + 61f, point.y + 12f);
canvas.drawOval(point.x-40f,point.y-60f,point.x+40f,point.y+40f, mPaint);
}
canvas.restore();
// eyes
mPaint.setColor(EYE_COLOR);
if (mBlinking) {
drawPupil(canvas, point.x - 16f, point.y - 12f, 6f, false, mPaint);
drawPupil(canvas, point.x + 16f, point.y - 12f, 6f, false, mPaint);
} else {
canvas.drawCircle(point.x - 16f, point.y - 12f, 6f, mPaint);
canvas.drawCircle(point.x + 16f, point.y - 12f, 6f, mPaint);
}
// too much?
if (false) {
mPaint.setColor(0xFF000000);
drawPupil(canvas, point.x - 16f, point.y - 12f, 5f, true, mPaint);
drawPupil(canvas, point.x + 16f, point.y - 12f, 5f, true, mPaint);
}
// arms in front
mPaint.setColor(ARM_COLOR);
for (int i : FRONT_ARMS) {
mArms[i].draw(canvas, mPaint);
}
if (PATH_DEBUG) for (Arm arm : mArms) {
arm.drawDebug(canvas);
}
}
canvas.restore();
}
public void setBlinking(boolean b) {
mBlinking = b;
invalidateSelf();
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
static Path pathMoveTo(Path p, PointF pt) {
p.moveTo(pt.x, pt.y);
return p;
}
static Path pathQuadTo(Path p, PointF p1, PointF p2) {
p.quadTo(p1.x, p1.y, p2.x, p2.y);
return p;
}
static void mapPointF(Matrix m, PointF point) {
float[] p = new float[2];
p[0] = point.x;
p[1] = point.y;
m.mapPoints(p);
point.x = p[0];
point.y = p[1];
}
private class Link // he come to town
implements DynamicAnimation.OnAnimationUpdateListener {
final FloatValueHolder[] coords = new FloatValueHolder[2];
final SpringAnimation[] anims = new SpringAnimation[coords.length];
private float dx, dy;
private boolean locked = false;
Link next;
Link(int index, float x1, float y1, float dx, float dy) {
coords[0] = new FloatValueHolder(x1);
coords[1] = new FloatValueHolder(y1);
this.dx = dx;
this.dy = dy;
for (int i=0; i<coords.length; i++) {
anims[i] = new SpringAnimation(coords[i]);
anims[i].setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(
index == 0 ? SpringForce.STIFFNESS_LOW
: index == 1 ? SpringForce.STIFFNESS_VERY_LOW
: SpringForce.STIFFNESS_VERY_LOW/2)
.setFinalPosition(0f));
anims[i].addUpdateListener(this);
}
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public PointF start() {
return new PointF(coords[0].getValue(), coords[1].getValue());
}
public PointF end() {
return new PointF(coords[0].getValue()+dx,coords[1].getValue()+dy);
}
public PointF mid() {
return new PointF(
0.5f*dx+(coords[0].getValue()),
0.5f*dy+(coords[1].getValue()));
}
public void animateTo(PointF target) {
if (locked) {
setStart(target.x, target.y);
} else {
anims[0].animateToFinalPosition(target.x);
anims[1].animateToFinalPosition(target.y);
}
}
@Override
public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float v, float v1) {
if (next != null) {
next.animateTo(end());
}
OctopusDrawable.this.invalidateSelf();
}
public void setStart(float x, float y) {
coords[0].setValue(x);
coords[1].setValue(y);
onAnimationUpdate(null, 0, 0);
}
}
private class Arm {
final Link link1, link2, link3;
float max, min;
public Arm(float x, float y, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3,
float max, float min) {
link1 = new Link(0, x, y, dx1, dy1);
link2 = new Link(1, x+dx1, y+dy1, dx2, dy2);
link3 = new Link(2, x+dx1+dx2, y+dy1+dy2, dx3, dy3);
link1.next = link2;
link2.next = link3;
link1.setLocked(true);
link2.setLocked(false);
link3.setLocked(false);
this.max = max;
this.min = min;
}
// when the arm is locked, it moves rigidly, without physics
public void setLocked(boolean locked) {
link2.setLocked(locked);
link3.setLocked(locked);
}
private void setAnchor(float x, float y) {
link1.setStart(x,y);
}
public Path getPath() {
Path p = new Path();
pathMoveTo(p, link1.start());
pathQuadTo(p, link2.start(), link2.mid());
pathQuadTo(p, link2.end(), link3.end());
return p;
}
public void draw(@NonNull Canvas canvas, Paint pt) {
final Path p = getPath();
TaperedPathStroke.drawPath(canvas, p, max, min, pt);
}
private final Paint dpt = new Paint();
public void drawDebug(Canvas canvas) {
dpt.setStyle(Paint.Style.STROKE);
dpt.setStrokeWidth(0.75f);
dpt.setStrokeCap(Paint.Cap.ROUND);
dpt.setAntiAlias(true);
dpt.setColor(0xFF336699);
final Path path = getPath();
canvas.drawPath(path, dpt);
dpt.setColor(0xFFFFFF00);
dpt.setPathEffect(new DashPathEffect(new float[] {2f, 2f}, 0f));
canvas.drawLines(new float[] {
link1.end().x, link1.end().y,
link2.start().x, link2.start().y,
link2.end().x, link2.end().y,
link3.start().x, link3.start().y,
}, dpt);
dpt.setPathEffect(null);
dpt.setColor(0xFF00CCFF);
canvas.drawLines(new float[] {
link1.start().x, link1.start().y,
link1.end().x, link1.end().y,
link2.start().x, link2.start().y,
link2.end().x, link2.end().y,
link3.start().x, link3.start().y,
link3.end().x, link3.end().y,
}, dpt);
dpt.setColor(0xFFCCEEFF);
canvas.drawCircle(link2.start().x, link2.start().y, 2f, dpt);
canvas.drawCircle(link3.start().x, link3.start().y, 2f, dpt);
dpt.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(link1.start().x, link1.start().y, 2f, dpt);
canvas.drawCircle(link2.mid().x, link2.mid().y, 2f, dpt);
canvas.drawCircle(link3.end().x, link3.end().y, 2f, dpt);
}
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2017 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.egg.octo;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.os.Debug;
import java.util.Arrays;
public class TaperedPathStroke {
static float sMinStepPx = 4f;
static PathMeasure pm = new PathMeasure();
static float[] pos = {0,0};
static float[] tan = {0,0};
static float lerp(float t, float a, float b) {
return a + t*(b-a);
}
public static void setMinStep(float px) {
sMinStepPx = px;
}
// it's the variable-width brush algorithm from the Markers app, basically
public static void drawPath(Canvas c, Path p, float r1, float r2, Paint pt) {
pm.setPath(p,false);
final float len = pm.getLength();
float t=0;
boolean last=false;
while (true) {
if (t>=len) {
t=len;
last=true;
}
pm.getPosTan(t, pos, tan);
float r = len > 0 ? lerp(t/len, r1, r2) : r1;
c.drawCircle(pos[0], pos[1], r, pt);
t += Math.max(r*0.25f, sMinStepPx); // walk forward 1/4 radius, not too small though
if (last) break;
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.egg.paint
import android.content.Context
import android.graphics.*
import android.graphics.PixelFormat.TRANSLUCENT
import android.graphics.drawable.Drawable
import android.util.DisplayMetrics
class BrushPropertyDrawable : Drawable {
val framePaint = Paint(Paint.ANTI_ALIAS_FLAG).also {
it.color = Color.BLACK
it.style = Paint.Style.FILL
}
val wellPaint = Paint(Paint.ANTI_ALIAS_FLAG).also {
it.color = Color.RED
it.style = Paint.Style.FILL
}
constructor(context: Context) {
_size = (24 * context.resources.displayMetrics.density).toInt()
}
private var _size = 24
private var _scale = 1f
fun setFrameColor(color: Int) {
framePaint.color = color
invalidateSelf()
}
fun setWellColor(color: Int) {
wellPaint.color = color
invalidateSelf()
}
fun setWellScale(scale: Float) {
_scale = scale
invalidateSelf()
}
override fun getIntrinsicWidth(): Int {
return _size
}
override fun getIntrinsicHeight(): Int {
return _size
}
override fun draw(c: Canvas?) {
c?.let {
val w = bounds.width().toFloat()
val h = bounds.height().toFloat()
val inset = _size / 12 // 2dp in a 24x24 icon
val r = Math.min(w, h) / 2
c.drawCircle(w/2, h/2, (r - inset) * _scale + 1 , wellPaint)
val p = Path()
p.addCircle(w/2, h/2, r, Path.Direction.CCW)
p.addCircle(w/2, h/2, r - inset, Path.Direction.CW)
c.drawPath(p, framePaint)
}
}
override fun setAlpha(p0: Int) {
//
}
override fun getOpacity(): Int {
return TRANSLUCENT
}
override fun setColorFilter(p0: ColorFilter?) {
//
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.egg.paint
import android.content.Context
import android.util.AttributeSet
import android.view.*
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.LinearLayout
class CutoutAvoidingToolbar : LinearLayout {
private var _insets: WindowInsets? = null
constructor(context: Context) : super(context) {
init(null, 0)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(attrs, 0)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init(attrs, defStyle)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
adjustLayout()
}
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
_insets = insets
adjustLayout()
return super.onApplyWindowInsets(insets)
}
fun adjustLayout() {
_insets?.displayCutout?.boundingRects?.let {
var cutoutCenter = 0
var cutoutLeft = 0
var cutoutRight = 0
// collect at most three cutouts
for (r in it) {
if (r.top > 0) continue
if (r.left == left) {
cutoutLeft = r.width()
} else if (r.right == right) {
cutoutRight = r.width()
} else {
cutoutCenter = r.width()
}
}
// apply to layout
(findViewWithTag("cutoutLeft") as View?)?.let {
it.layoutParams = LayoutParams(cutoutLeft, MATCH_PARENT)
}
(findViewWithTag("cutoutCenter") as View?)?.let {
it.layoutParams = LayoutParams(cutoutCenter, MATCH_PARENT)
}
(findViewWithTag("cutoutRight") as View?)?.let {
it.layoutParams = LayoutParams(cutoutRight, MATCH_PARENT)
}
requestLayout()
}
}
private fun init(attrs: AttributeSet?, defStyle: Int) {
}
}

View File

@@ -0,0 +1,351 @@
/*
* 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.egg.paint;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Magnifier;
import com.android.egg.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.IntStream;
public class PaintActivity extends Activity {
private static final float MAX_BRUSH_WIDTH_DP = 100f;
private static final float MIN_BRUSH_WIDTH_DP = 1f;
private static final int NUM_BRUSHES = 6;
private static final int NUM_COLORS = 6;
private Painting painting = null;
private CutoutAvoidingToolbar toolbar = null;
private LinearLayout brushes = null;
private LinearLayout colors = null;
private Magnifier magnifier = null;
private boolean sampling = false;
private View.OnClickListener buttonHandler = new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btnBrush:
view.setSelected(true);
hideToolbar(colors);
toggleToolbar(brushes);
break;
case R.id.btnColor:
view.setSelected(true);
hideToolbar(brushes);
toggleToolbar(colors);
break;
case R.id.btnClear:
painting.clear();
break;
case R.id.btnSample:
sampling = true;
view.setSelected(true);
break;
case R.id.btnZen:
painting.setZenMode(!painting.getZenMode());
view.animate()
.setStartDelay(200)
.setInterpolator(new OvershootInterpolator())
.rotation(painting.getZenMode() ? 0f : 90f);
break;
}
}
};
private void showToolbar(View bar) {
if (bar.getVisibility() != View.GONE) return;
bar.setVisibility(View.VISIBLE);
bar.setTranslationY(toolbar.getHeight()/2);
bar.animate()
.translationY(toolbar.getHeight())
.alpha(1f)
.setDuration(220)
.start();
}
private void hideToolbar(View bar) {
if (bar.getVisibility() != View.VISIBLE) return;
bar.animate()
.translationY(toolbar.getHeight()/2)
.alpha(0f)
.setDuration(150)
.withEndAction(new Runnable() {
@Override
public void run() {
bar.setVisibility(View.GONE);
}
})
.start();
}
private void toggleToolbar(View bar) {
if (bar.getVisibility() == View.VISIBLE) {
hideToolbar(bar);
} else {
showToolbar(bar);
}
}
private BrushPropertyDrawable widthButtonDrawable;
private BrushPropertyDrawable colorButtonDrawable;
private float maxBrushWidth, minBrushWidth;
private int nightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
static final float lerp(float f, float a, float b) {
return a + (b-a) * f;
}
void setupViews(Painting oldPainting) {
setContentView(R.layout.activity_paint);
painting = oldPainting != null ? oldPainting : new Painting(this);
((FrameLayout) findViewById(R.id.contentView)).addView(painting,
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
painting.setPaperColor(getColor(R.color.paper_color));
painting.setPaintColor(getColor(R.color.paint_color));
toolbar = findViewById(R.id.toolbar);
brushes = findViewById(R.id.brushes);
colors = findViewById(R.id.colors);
magnifier = new Magnifier(painting);
painting.setOnTouchListener(
new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getActionMasked()) {
case ACTION_DOWN:
case ACTION_MOVE:
if (sampling) {
magnifier.show(event.getX(), event.getY());
colorButtonDrawable.setWellColor(
painting.sampleAt(event.getX(), event.getY()));
return true;
}
break;
case ACTION_CANCEL:
if (sampling) {
findViewById(R.id.btnSample).setSelected(false);
sampling = false;
magnifier.dismiss();
}
break;
case ACTION_UP:
if (sampling) {
findViewById(R.id.btnSample).setSelected(false);
sampling = false;
magnifier.dismiss();
painting.setPaintColor(
painting.sampleAt(event.getX(), event.getY()));
refreshBrushAndColor();
}
break;
}
return false; // allow view to continue handling
}
});
findViewById(R.id.btnBrush).setOnClickListener(buttonHandler);
findViewById(R.id.btnColor).setOnClickListener(buttonHandler);
findViewById(R.id.btnClear).setOnClickListener(buttonHandler);
findViewById(R.id.btnSample).setOnClickListener(buttonHandler);
findViewById(R.id.btnZen).setOnClickListener(buttonHandler);
findViewById(R.id.btnColor).setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
colors.removeAllViews();
showToolbar(colors);
refreshBrushAndColor();
return true;
}
});
findViewById(R.id.btnClear).setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
painting.invertContents();
return true;
}
});
widthButtonDrawable = new BrushPropertyDrawable(this);
widthButtonDrawable.setFrameColor(getColor(R.color.toolbar_icon_color));
colorButtonDrawable = new BrushPropertyDrawable(this);
colorButtonDrawable.setFrameColor(getColor(R.color.toolbar_icon_color));
((ImageButton) findViewById(R.id.btnBrush)).setImageDrawable(widthButtonDrawable);
((ImageButton) findViewById(R.id.btnColor)).setImageDrawable(colorButtonDrawable);
refreshBrushAndColor();
}
private void refreshBrushAndColor() {
final LinearLayout.LayoutParams button_lp = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT);
button_lp.weight = 1f;
if (brushes.getChildCount() == 0) {
for (int i = 0; i < NUM_BRUSHES; i++) {
final BrushPropertyDrawable icon = new BrushPropertyDrawable(this);
icon.setFrameColor(getColor(R.color.toolbar_icon_color));
// exponentially increasing brush size
final float width = lerp(
(float) Math.pow((float) i / NUM_BRUSHES, 2f), minBrushWidth,
maxBrushWidth);
icon.setWellScale(width / maxBrushWidth);
icon.setWellColor(getColor(R.color.toolbar_icon_color));
final ImageButton button = new ImageButton(this);
button.setImageDrawable(icon);
button.setBackground(getDrawable(R.drawable.toolbar_button_bg));
button.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
brushes.setSelected(false);
hideToolbar(brushes);
painting.setBrushWidth(width);
refreshBrushAndColor();
}
});
brushes.addView(button, button_lp);
}
}
if (colors.getChildCount() == 0) {
final Palette pal = new Palette(NUM_COLORS);
for (final int c : IntStream.concat(
IntStream.of(Color.BLACK, Color.WHITE),
Arrays.stream(pal.getColors())
).toArray()) {
final BrushPropertyDrawable icon = new BrushPropertyDrawable(this);
icon.setFrameColor(getColor(R.color.toolbar_icon_color));
icon.setWellColor(c);
final ImageButton button = new ImageButton(this);
button.setImageDrawable(icon);
button.setBackground(getDrawable(R.drawable.toolbar_button_bg));
button.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
colors.setSelected(false);
hideToolbar(colors);
painting.setPaintColor(c);
refreshBrushAndColor();
}
});
colors.addView(button, button_lp);
}
}
widthButtonDrawable.setWellScale(painting.getBrushWidth() / maxBrushWidth);
widthButtonDrawable.setWellColor(painting.getPaintColor());
colorButtonDrawable.setWellColor(painting.getPaintColor());
}
private void refreshNightMode(Configuration config) {
int newNightMode =
(config.uiMode & Configuration.UI_MODE_NIGHT_MASK);
if (nightMode != newNightMode) {
if (nightMode != Configuration.UI_MODE_NIGHT_UNDEFINED) {
painting.invertContents();
((ViewGroup) painting.getParent()).removeView(painting);
setupViews(painting);
final View decorView = getWindow().getDecorView();
int decorSUIV = decorView.getSystemUiVisibility();
if (newNightMode == Configuration.UI_MODE_NIGHT_YES) {
decorView.setSystemUiVisibility(
decorSUIV & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
} else {
decorView.setSystemUiVisibility(
decorSUIV | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
}
nightMode = newNightMode;
}
}
public PaintActivity() {
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
painting.onTrimMemory();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
refreshNightMode(newConfig);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.flags = lp.flags
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
getWindow().setAttributes(lp);
maxBrushWidth = MAX_BRUSH_WIDTH_DP * getResources().getDisplayMetrics().density;
minBrushWidth = MIN_BRUSH_WIDTH_DP * getResources().getDisplayMetrics().density;
setupViews(null);
refreshNightMode(getResources().getConfiguration());
}
@Override
public void onPostResume() {
super.onPostResume();
}
}

View File

@@ -0,0 +1,358 @@
/*
* 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.egg.paint
import android.content.Context
import android.content.res.Resources
import android.graphics.*
import android.provider.Settings
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.view.WindowInsets
import java.util.concurrent.TimeUnit
import android.util.Log
import android.provider.Settings.System
import org.json.JSONObject
fun hypot(x: Float, y: Float): Float {
return Math.hypot(x.toDouble(), y.toDouble()).toFloat()
}
fun invlerp(x: Float, a: Float, b: Float): Float {
return if (b > a) {
(x - a) / (b - a)
} else 1.0f
}
public class Painting : View, SpotFilter.Plotter {
companion object {
val FADE_MINS = TimeUnit.MINUTES.toMillis(3) // about how long a drawing should last
val ZEN_RATE = TimeUnit.SECONDS.toMillis(2) // how often to apply the fade
val ZEN_FADE = Math.max(1f, ZEN_RATE / FADE_MINS * 255f)
val FADE_TO_WHITE_CF = ColorMatrixColorFilter(ColorMatrix(floatArrayOf(
1f, 0f, 0f, 0f, ZEN_FADE,
0f, 1f, 0f, 0f, ZEN_FADE,
0f, 0f, 1f, 0f, ZEN_FADE,
0f, 0f, 0f, 1f, 0f
)))
val FADE_TO_BLACK_CF = ColorMatrixColorFilter(ColorMatrix(floatArrayOf(
1f, 0f, 0f, 0f, -ZEN_FADE,
0f, 1f, 0f, 0f, -ZEN_FADE,
0f, 0f, 1f, 0f, -ZEN_FADE,
0f, 0f, 0f, 1f, 0f
)))
val INVERT_CF = ColorMatrixColorFilter(ColorMatrix(floatArrayOf(
-1f, 0f, 0f, 0f, 255f,
0f, -1f, 0f, 0f, 255f,
0f, 0f, -1f, 0f, 255f,
0f, 0f, 0f, 1f, 0f
)))
val TOUCH_STATS = "touch.stats" // Settings.System key
}
var devicePressureMin = 0f; // ideal value
var devicePressureMax = 1f; // ideal value
var zenMode = true
set(value) {
if (field != value) {
field = value
removeCallbacks(fadeRunnable)
if (value && isAttachedToWindow) {
handler.postDelayed(fadeRunnable, ZEN_RATE)
}
}
}
var bitmap: Bitmap? = null
var paperColor : Int = 0xFFFFFFFF.toInt()
private var _paintCanvas: Canvas? = null
private val _bitmapLock = Object()
private var _drawPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var _lastX = 0f
private var _lastY = 0f
private var _lastR = 0f
private var _insets: WindowInsets? = null
private var _brushWidth = 100f
private var _filter = SpotFilter(10, 0.5f, 0.9f, this)
private val fadeRunnable = object : Runnable {
private val pt = Paint()
override fun run() {
val c = _paintCanvas
if (c != null) {
pt.colorFilter =
if (paperColor.and(0xFF) > 0x80)
FADE_TO_WHITE_CF
else
FADE_TO_BLACK_CF
synchronized(_bitmapLock) {
c.drawBitmap(bitmap, 0f, 0f, pt)
}
invalidate()
}
postDelayed(this, ZEN_RATE)
}
}
constructor(context: Context) : super(context) {
init(null, 0)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(attrs, 0)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init(attrs, defStyle)
}
private fun init(attrs: AttributeSet?, defStyle: Int) {
loadDevicePressureData()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setupBitmaps()
if (zenMode) {
handler.postDelayed(fadeRunnable, ZEN_RATE)
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
setupBitmaps()
}
override fun onDetachedFromWindow() {
if (zenMode) {
removeCallbacks(fadeRunnable)
}
super.onDetachedFromWindow()
}
fun onTrimMemory() {
}
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
_insets = insets
if (insets != null && _paintCanvas == null) {
setupBitmaps()
}
return super.onApplyWindowInsets(insets)
}
private fun powf(a: Float, b: Float): Float {
return Math.pow(a.toDouble(), b.toDouble()).toFloat()
}
override fun plot(s: MotionEvent.PointerCoords) {
val c = _paintCanvas
if (c == null) return
synchronized(_bitmapLock) {
var x = _lastX
var y = _lastY
var r = _lastR
val newR = Math.max(1f, powf(adjustPressure(s.pressure), 2f).toFloat() * _brushWidth)
if (r >= 0) {
val d = hypot(s.x - x, s.y - y)
if (d > 1f && (r + newR) > 1f) {
val N = (2 * d / Math.min(4f, r + newR)).toInt()
val stepX = (s.x - x) / N
val stepY = (s.y - y) / N
val stepR = (newR - r) / N
for (i in 0 until N - 1) { // we will draw the last circle below
x += stepX
y += stepY
r += stepR
c.drawCircle(x, y, r, _drawPaint)
}
}
}
c.drawCircle(s.x, s.y, newR, _drawPaint)
_lastX = s.x
_lastY = s.y
_lastR = newR
}
}
private fun loadDevicePressureData() {
try {
val touchDataJson = Settings.System.getString(context.contentResolver, TOUCH_STATS)
val touchData = JSONObject(
if (touchDataJson != null) touchDataJson else "{}")
if (touchData.has("min")) devicePressureMin = touchData.getDouble("min").toFloat()
if (touchData.has("max")) devicePressureMax = touchData.getDouble("max").toFloat()
if (devicePressureMin < 0) devicePressureMin = 0f
if (devicePressureMax < devicePressureMin) devicePressureMax = devicePressureMin + 1f
} catch (e: Exception) {
}
}
private fun adjustPressure(pressure: Float): Float {
if (pressure > devicePressureMax) devicePressureMax = pressure
if (pressure < devicePressureMin) devicePressureMin = pressure
return invlerp(pressure, devicePressureMin, devicePressureMax)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val c = _paintCanvas
if (event == null || c == null) return super.onTouchEvent(event)
/*
val pt = Paint(Paint.ANTI_ALIAS_FLAG)
pt.style = Paint.Style.STROKE
pt.color = 0x800000FF.toInt()
_paintCanvas?.drawCircle(event.x, event.y, 20f, pt)
*/
when (event.actionMasked) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
_filter.add(event)
_filter.finish()
invalidate()
}
MotionEvent.ACTION_DOWN -> {
_lastR = -1f
_filter.add(event)
invalidate()
}
MotionEvent.ACTION_MOVE -> {
_filter.add(event)
invalidate()
}
}
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
bitmap?.let {
canvas.drawBitmap(bitmap, 0f, 0f, _drawPaint);
}
}
// public api
fun clear() {
bitmap = null
setupBitmaps()
invalidate()
}
fun sampleAt(x: Float, y: Float): Int {
val localX = (x - left).toInt()
val localY = (y - top).toInt()
return bitmap?.getPixel(localX, localY) ?: Color.BLACK
}
fun setPaintColor(color: Int) {
_drawPaint.color = color
}
fun getPaintColor(): Int {
return _drawPaint.color
}
fun setBrushWidth(w: Float) {
_brushWidth = w
}
fun getBrushWidth(): Float {
return _brushWidth
}
private fun setupBitmaps() {
val dm = DisplayMetrics()
display.getRealMetrics(dm)
val w = dm.widthPixels
val h = dm.heightPixels
val oldBits = bitmap
var bits = oldBits
if (bits == null || bits.width != w || bits.height != h) {
bits = Bitmap.createBitmap(
w,
h,
Bitmap.Config.ARGB_8888
)
}
if (bits == null) return
val c = Canvas(bits)
if (oldBits != null) {
if (oldBits.width < oldBits.height != bits.width < bits.height) {
// orientation change. let's rotate things so they fit better
val matrix = Matrix()
if (bits.width > bits.height) {
// now landscape
matrix.postRotate(-90f)
matrix.postTranslate(0f, bits.height.toFloat())
} else {
// now portrait
matrix.postRotate(90f)
matrix.postTranslate(bits.width.toFloat(), 0f)
}
if (bits.width != oldBits.height || bits.height != oldBits.width) {
matrix.postScale(
bits.width.toFloat()/oldBits.height,
bits.height.toFloat()/oldBits.width)
}
c.matrix = matrix
}
// paint the old artwork atop the new
c.drawBitmap(oldBits, 0f, 0f, _drawPaint)
c.matrix = Matrix()
} else {
c.drawColor(paperColor)
}
bitmap = bits
_paintCanvas = c
}
fun invertContents() {
val invertPaint = Paint()
invertPaint.colorFilter = INVERT_CF
synchronized(_bitmapLock) {
_paintCanvas?.drawBitmap(bitmap, 0f, 0f, invertPaint)
}
invalidate()
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.egg.paint
import android.graphics.Color
class Palette {
var colors : IntArray
var lightest = 0
var darkest = 0
/**
* rough luminance calculation
* https://www.w3.org/TR/AERT/#color-contrast
*/
private fun lum(rgb: Int): Float {
return (Color.red(rgb) * 299f + Color.green(rgb) * 587f + Color.blue(rgb) * 114f) / 1000f
}
/**
* create a random evenly-spaced color palette
* guaranteed to contrast!
*/
fun randomize(S: Float, V: Float) {
val hsv = floatArrayOf((Math.random() * 360f).toFloat(), S, V)
val count = colors.size
colors[0] = Color.HSVToColor(hsv)
lightest = 0
darkest = 0
for (i in 0 until count) {
hsv[0] = (hsv[0] + 360f / count).rem(360f)
val color = Color.HSVToColor(hsv)
colors[i] = color
val lum = lum(colors[i])
if (lum < lum(colors[darkest])) darkest = i
if (lum > lum(colors[lightest])) lightest = i
}
}
override fun toString() : String {
val str = StringBuilder("Palette{ ")
for (c in colors) {
str.append(String.format("#%08x ", c))
}
str.append("}")
return str.toString()
}
constructor(count: Int) {
colors = IntArray(count)
randomize(1f, 1f)
}
constructor(count: Int, S: Float, V: Float) {
colors = IntArray(count)
randomize(S, V)
}
constructor(_colors: IntArray) {
colors = _colors
for (i in 0 until colors.size) {
val lum = lum(colors[i])
if (lum < lum(colors[darkest])) darkest = i
if (lum > lum(colors[lightest])) lightest = i
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.egg.paint
import java.util.LinkedList
import android.view.MotionEvent
class SpotFilter(internal var mBufSize: Int, posDecay: Float, pressureDecay: Float, internal var mPlotter: Plotter) {
val spots = LinkedList<MotionEvent.PointerCoords>() // newest at front
val tmpSpot = MotionEvent.PointerCoords()
var lastTool = MotionEvent.TOOL_TYPE_UNKNOWN
val posDecay: Float
val pressureDecay: Float
interface Plotter {
fun plot(s: MotionEvent.PointerCoords)
}
init {
this.posDecay = if (posDecay in 0f..1f) posDecay else 1f
this.pressureDecay = if (pressureDecay in 0f..1f) pressureDecay else 1f
}
fun filterInto(out: MotionEvent.PointerCoords, tool: Int): MotionEvent.PointerCoords {
lastTool = tool
var wi = 1f // weight for ith component (position)
var w = 0f // total weight
var wi_press = 1f // weight for ith component (pressure)
var w_press = 0f // total weight (pressure)
var x = 0f
var y = 0f
var pressure = 0f
var size = 0f
for (pi in spots) {
x += pi.x * wi
y += pi.y * wi
pressure += pi.pressure * wi_press
size += pi.size * wi_press
w += wi
wi *= posDecay // exponential backoff
w_press += wi_press
wi_press *= pressureDecay
if (PRECISE_STYLUS_INPUT && tool == MotionEvent.TOOL_TYPE_STYLUS) {
// just take the newest one, no need to average
break
}
}
out.x = x / w
out.y = y / w
out.pressure = pressure / w_press
out.size = size / w_press
return out
}
protected fun addInternal(c: MotionEvent.PointerCoords, tool: Int) {
val coord =
if (spots.size == mBufSize) {
spots.removeLast()
} else {
MotionEvent.PointerCoords()
}
coord.copyFrom(c)
spots.add(0, coord)
filterInto(tmpSpot, tool)
mPlotter.plot(tmpSpot)
}
fun add(cv: List<MotionEvent.PointerCoords>, tool: Int) {
for (c in cv) {
addInternal(c, tool)
}
}
fun add(evt: MotionEvent) {
val tool = evt.getToolType(0)
for (i in 0 until evt.historySize) {
evt.getHistoricalPointerCoords(0, i, tmpSpot)
addInternal(tmpSpot, tool)
}
evt.getPointerCoords(0, tmpSpot)
addInternal(tmpSpot, tool)
}
fun finish() {
while (spots.size > 0) {
filterInto(tmpSpot, lastTool)
spots.removeLast()
mPlotter.plot(tmpSpot)
}
spots.clear()
}
companion object {
var PRECISE_STYLUS_INPUT = true
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.egg.paint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.text.TextPaint
import android.transition.ChangeBounds
import android.transition.Transition
import android.transition.TransitionListenerAdapter
import android.transition.TransitionManager
import android.util.AttributeSet
import android.view.*
import android.view.animation.OvershootInterpolator
import android.widget.FrameLayout
class ToolbarView : FrameLayout {
var inTransition = false
var transitionListener: Transition.TransitionListener = object : TransitionListenerAdapter() {
override fun onTransitionStart(transition: Transition?) {
inTransition = true
}
override fun onTransitionEnd(transition: Transition?) {
inTransition = false
}
}
constructor(context: Context) : super(context) {
init(null, 0)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(attrs, 0)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init(attrs, defStyle)
}
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
var lp = layoutParams as FrameLayout.LayoutParams?
if (lp != null && insets != null) {
if (insets.hasStableInsets()) {
lp.topMargin = insets.stableInsetTop
lp.bottomMargin = insets.stableInsetBottom
} else {
lp.topMargin = insets.systemWindowInsetTop
lp.bottomMargin = insets.systemWindowInsetBottom
}
layoutParams = lp
}
return super.onApplyWindowInsets(insets)
}
private fun init(attrs: AttributeSet?, defStyle: Int) {
}
}