aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android_sample/SampleApp/Android.mk70
-rw-r--r--android_sample/SampleApp/AndroidManifest.xml32
-rw-r--r--android_sample/SampleApp/README.txt25
-rw-r--r--android_sample/SampleApp/jni/sample-jni.cpp208
-rw-r--r--android_sample/SampleApp/res/layout/layout.xml26
-rw-r--r--android_sample/SampleApp/res/values/strings.xml18
-rw-r--r--android_sample/SampleApp/src/com/skia/sampleapp/SampleApp.java114
-rw-r--r--include/utils/android/AndroidKeyToSkKey.h40
-rw-r--r--include/views/SkOSWindow_Android.h41
-rw-r--r--include/views/SkWindow.h2
10 files changed, 576 insertions, 0 deletions
diff --git a/android_sample/SampleApp/Android.mk b/android_sample/SampleApp/Android.mk
new file mode 100644
index 0000000000..4df3defe43
--- /dev/null
+++ b/android_sample/SampleApp/Android.mk
@@ -0,0 +1,70 @@
+######################################
+# Build the app.
+######################################
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := SampleApp
+
+LOCAL_JNI_SHARED_LIBRARIES := libskia-sample
+
+include $(BUILD_PACKAGE)
+
+######################################
+# Build the shared library.
+######################################
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_C_INCLUDES += \
+ external/skia/include/core \
+ external/skia/include/config \
+ external/skia/include/effects \
+ external/skia/include/images \
+ external/skia/include/utils \
+ $(LOCAL_PATH)/skia_extra/include/views \
+ $(LOCAL_PATH)/skia_extra/samplecode \
+ $(LOCAL_PATH)/skia_extra/include/xml \
+ external/skia/include/gpu \
+ external/skia/src/core \
+ external/skia/gpu/include \
+ frameworks/base/core/jni/android/graphics \
+ frameworks/base/native/include/android \
+ $(LOCAL_PATH)/jni
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libutils \
+ libskia \
+ libandroid_runtime \
+ libGLESv2
+
+LOCAL_STATIC_LIBRARIES := \
+ libskiagpu
+
+LOCAL_PRELINK_MODULE := false
+
+LOCAL_MODULE := libskia-sample
+
+LOCAL_SRC_FILES := \
+ skia_extra/src/ports/SkXMLParser_empty.cpp \
+ jni/sample-jni.cpp
+
+include $(LOCAL_PATH)/skia_extra/src/views/views_files.mk
+LOCAL_SRC_FILES += $(addprefix skia_extra/src/views/, $(SOURCE))
+
+include $(LOCAL_PATH)/skia_extra/src/xml/xml_files.mk
+LOCAL_SRC_FILES += $(addprefix skia_extra/src/xml/, $(SOURCE))
+
+include $(LOCAL_PATH)/skia_extra/samplecode/samplecode_files.mk
+LOCAL_SRC_FILES += $(addprefix skia_extra/samplecode/, $(SOURCE))
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/android_sample/SampleApp/AndroidManifest.xml b/android_sample/SampleApp/AndroidManifest.xml
new file mode 100644
index 0000000000..e3232468f6
--- /dev/null
+++ b/android_sample/SampleApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 Skia
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.skia.sampleapp"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-sdk android:minSdkVersion="3" />
+ <application android:label="@string/app_name"
+ android:debuggable="true">
+ <activity android:name=".SampleApp"
+ android:theme="@android:style/Theme.Holo.NoActionBar"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/android_sample/SampleApp/README.txt b/android_sample/SampleApp/README.txt
new file mode 100644
index 0000000000..a7d8e54d2e
--- /dev/null
+++ b/android_sample/SampleApp/README.txt
@@ -0,0 +1,25 @@
+Building the sample app for Android using an Android tree:
+
+Copy this folder into an Android tree in packages/apps. In addition to jni,
+res, and src, there needs to be a fourth folder named "skia_extra". This
+will include the skia files which are not part of an Android checkout. It
+should have three folders: include, samplecode, and src.
+
+skia/trunk/include/views -> skia_extra/include/views
+skia/trunk/include/xml -> skia_extra/include/xml
+
+skia/trunk/samplecode -> skia_extra/samplecode
+
+skia/trunk/src/views -> skia_extra/src/views
+skia/trunk/src/ports/SkXMLParser_empty.cpp -> skia_extra/src/ports/
+skia/trunk/src/xml -> skia_extra/src/xml
+
+skia/trunk/include/utils/android/AndroidKeyToSkKey.h -> jni/
+
+From packages/apps/SampleApp, type "mm" to build, and install the
+resulting apk.
+
+(It may be necessary to remove samples that do not build from
+skia_extra/samplecode/samplecode_files.mk)
+
+TODO: Instructions for building from SDK/NDK
diff --git a/android_sample/SampleApp/jni/sample-jni.cpp b/android_sample/SampleApp/jni/sample-jni.cpp
new file mode 100644
index 0000000000..f8490b879a
--- /dev/null
+++ b/android_sample/SampleApp/jni/sample-jni.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2011 Skia
+ *
+ * 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.
+ *
+ */
+
+#include <jni.h>
+
+#include "SkCanvas.h"
+#include "GraphicsJNI.h"
+#include "SkEvent.h"
+#include "SkWindow.h"
+#include "SkApplication.h"
+#include "AndroidKeyToSkKey.h"
+
+///////////////////////////////////////////
+///////////////// Globals /////////////////
+///////////////////////////////////////////
+
+struct ActivityGlue {
+ JNIEnv* m_env;
+ jweak m_obj;
+ jmethodID m_setTitle;
+ ActivityGlue() {
+ m_env = NULL;
+ m_obj = NULL;
+ m_setTitle = NULL;
+ }
+} gActivityGlue;
+
+struct WindowGlue {
+ jweak m_obj;
+ jmethodID m_inval;
+ WindowGlue() {
+ m_obj = NULL;
+ m_inval = NULL;
+ }
+} gWindowGlue;
+
+SkOSWindow* gWindow;
+
+///////////////////////////////////////////
+///////////// SkOSWindow impl /////////////
+///////////////////////////////////////////
+
+void SkOSWindow::onSetTitle(const char title[])
+{
+ if (gActivityGlue.m_env) {
+ JNIEnv* env = gActivityGlue.m_env;
+ jstring string = env->NewStringUTF(title);
+ env->CallVoidMethod(gActivityGlue.m_obj, gActivityGlue.m_setTitle,
+ string);
+ env->DeleteLocalRef(string);
+ }
+}
+
+void SkOSWindow::onHandleInval(const SkIRect& rect)
+{
+ if (!gActivityGlue.m_env || !gWindowGlue.m_inval || !gWindowGlue.m_obj) {
+ return;
+ }
+ gActivityGlue.m_env->CallVoidMethod(gWindowGlue.m_obj, gWindowGlue.m_inval,
+ rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+
+///////////////////////////////////////////
+/////////////// SkEvent impl //////////////
+///////////////////////////////////////////
+
+void SkEvent::SignalQueueTimer(SkMSec) {}
+
+void SkEvent::SignalNonEmptyQueue() {}
+
+///////////////////////////////////////////
+////////////////// JNI ////////////////////
+///////////////////////////////////////////
+
+static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[],
+ const char signature[])
+{
+ jmethodID m = env->GetMethodID(clazz, name, signature);
+ if (!m) SkDebugf("Could not find Java method %s\n", name);
+ return m;
+}
+
+extern "C" {
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_drawToCanvas(
+ JNIEnv* env, jobject thiz, jobject jcanvas);
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_init(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_term(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_updateSize(
+ JNIEnv* env, jobject thiz, jint w, jint h);
+JNIEXPORT bool JNICALL Java_com_skia_sampleapp_SampleApp_handleKeyDown(
+ JNIEnv* env, jobject thiz, jint keyCode, jint uni);
+JNIEXPORT bool JNICALL Java_com_skia_sampleapp_SampleApp_handleKeyUp(
+ JNIEnv* env, jobject thiz, jint keyCode);
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_handleClick(
+ JNIEnv* env, jobject thiz, jint x, jint y, jint state);
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_createOSWindow(
+ JNIEnv* env, jobject thiz, jobject jsampleView);
+};
+
+JNIEXPORT bool JNICALL Java_com_skia_sampleapp_SampleApp_handleKeyDown(
+ JNIEnv* env, jobject thiz, jint keyCode, jint uni)
+{
+ bool handled = gWindow->handleKey(AndroidKeycodeToSkKey(keyCode));
+ handled |= gWindow->handleChar((SkUnichar) uni);
+ return handled;
+}
+
+JNIEXPORT bool JNICALL Java_com_skia_sampleapp_SampleApp_handleKeyUp(JNIEnv* env,
+ jobject thiz, jint keyCode)
+{
+ return gWindow->handleKeyUp(AndroidKeycodeToSkKey(keyCode));
+}
+
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_handleClick(JNIEnv* env,
+ jobject thiz, jint x, jint y, jint jstate)
+{
+ SkView::Click::State state;
+ switch(jstate) {
+ case 0: // MotionEvent.ACTION_DOWN
+ state = SkView::Click::kDown_State;
+ break;
+ case 1: // MotionEvent.ACTION_UP
+ case 3: // MotionEvent.ACTION_CANCEL
+ state = SkView::Click::kUp_State;
+ break;
+ case 2: // MotionEvent.ACTION_MOVE
+ state = SkView::Click::kMoved_State;
+ break;
+ default:
+ SkDebugf("motion event ignored\n");
+ return;
+ }
+ gWindow->handleClick(x, y, state);
+}
+
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_updateSize(JNIEnv* env,
+ jobject thiz, jint w, jint h)
+{
+ gWindow->resize(w, h);
+}
+
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_createOSWindow(
+ JNIEnv* env, jobject thiz, jobject jsampleView)
+{
+ gWindow = create_sk_window(NULL);
+ // Only using a method on View.
+ jclass clazz = gActivityGlue.m_env->FindClass("android/view/View");
+ gWindowGlue.m_obj = gActivityGlue.m_env->NewWeakGlobalRef(jsampleView);
+ gWindowGlue.m_inval = GetJMethod(gActivityGlue.m_env, clazz, "invalidate",
+ "(IIII)V");
+ gActivityGlue.m_env->DeleteLocalRef(clazz);
+}
+
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_init(JNIEnv* env,
+ jobject thiz)
+{
+ gActivityGlue.m_env = env;
+ // Only using a method on Activity.
+ jclass clazz = env->FindClass("android/app/Activity");
+ gActivityGlue.m_obj = env->NewWeakGlobalRef(thiz);
+ gActivityGlue.m_setTitle = GetJMethod(env, clazz, "setTitle",
+ "(Ljava/lang/CharSequence;)V");
+ env->DeleteLocalRef(clazz);
+
+ application_init();
+}
+
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_term(JNIEnv* env,
+ jobject thiz)
+{
+ application_term();
+ if (gWindowGlue.m_obj) {
+ env->DeleteWeakGlobalRef(gWindowGlue.m_obj);
+ gWindowGlue.m_obj = NULL;
+ }
+ if (gActivityGlue.m_obj) {
+ env->DeleteWeakGlobalRef(gActivityGlue.m_obj);
+ gActivityGlue.m_obj = NULL;
+ }
+ delete gWindow;
+ gWindow = NULL;
+}
+
+
+JNIEXPORT void JNICALL Java_com_skia_sampleapp_SampleApp_drawToCanvas(
+ JNIEnv* env, jobject thiz, jobject jcanvas)
+{
+ if (!gWindow) return;
+ gWindow->update(NULL);
+ SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+ canvas->drawBitmap(gWindow->getBitmap(), 0, 0);
+}
diff --git a/android_sample/SampleApp/res/layout/layout.xml b/android_sample/SampleApp/res/layout/layout.xml
new file mode 100644
index 0000000000..d71116bae1
--- /dev/null
+++ b/android_sample/SampleApp/res/layout/layout.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 Skia
+
+ 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:id="@+id/holder"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView android:id="@+id/title_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+</LinearLayout>
+
diff --git a/android_sample/SampleApp/res/values/strings.xml b/android_sample/SampleApp/res/values/strings.xml
new file mode 100644
index 0000000000..72d3bc8596
--- /dev/null
+++ b/android_sample/SampleApp/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 Skia
+
+ 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>
+ <string name="app_name">SampleApp</string>
+</resources>
diff --git a/android_sample/SampleApp/src/com/skia/sampleapp/SampleApp.java b/android_sample/SampleApp/src/com/skia/sampleapp/SampleApp.java
new file mode 100644
index 0000000000..b02c4d7fe4
--- /dev/null
+++ b/android_sample/SampleApp/src/com/skia/sampleapp/SampleApp.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2011 Skia
+ *
+ * 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.skia.sampleapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class SampleApp extends Activity
+{
+ private TextView mTitle;
+
+ public class SampleView extends View {
+ public SampleView(Context context) {
+ super(context);
+ createOSWindow(this);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawToCanvas(canvas);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ updateSize(w, h);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ final int action = event.getAction();
+ handleClick(x, y, action);
+ return true;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ init();
+ setContentView(R.layout.layout);
+ mTitle = (TextView) findViewById(R.id.title_view);
+ LinearLayout holder = (LinearLayout) findViewById(R.id.holder);
+ View view = new SampleView(this);
+ holder.addView(view, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ term();
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ int uni = event.getUnicodeChar(event.getMetaState());
+ return handleKeyDown(event.getKeyCode(), uni);
+ case KeyEvent.ACTION_UP:
+ return handleKeyUp(event.getKeyCode());
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle.setText(title);
+ }
+
+ private native void drawToCanvas(Canvas canvas);
+ private native void init();
+ private native void term();
+ // Currently depends on init having already been called.
+ private native void createOSWindow(SampleView view);
+ private native void updateSize(int w, int h);
+ private native void handleClick(int x, int y, int state);
+ private native boolean handleKeyDown(int key, int uni);
+ private native boolean handleKeyUp(int key);
+
+ static {
+ System.loadLibrary("skia-sample");
+ }
+}
diff --git a/include/utils/android/AndroidKeyToSkKey.h b/include/utils/android/AndroidKeyToSkKey.h
new file mode 100644
index 0000000000..7b0a03570e
--- /dev/null
+++ b/include/utils/android/AndroidKeyToSkKey.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 Skia
+ *
+ * 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.
+ */
+
+#ifndef _ANDROID_TO_SKIA_KEYCODES_H
+#define _ANDROID_TO_SKIA_KEYCODES_H
+
+#include "keycodes.h"
+#include "SkKey.h"
+
+// Convert an Android keycode to an SkKey. This is an incomplete list, only
+// including keys used by the sample app.
+SkKey AndroidKeycodeToSkKey(int keycode) {
+ switch (keycode) {
+ case AKEYCODE_DPAD_LEFT:
+ return kLeft_SkKey;
+ case AKEYCODE_DPAD_RIGHT:
+ return kRight_SkKey;
+ case AKEYCODE_DPAD_UP:
+ return kUp_SkKey;
+ case AKEYCODE_DPAD_DOWN:
+ return kDown_SkKey;
+ default:
+ return kNONE_SkKey;
+ }
+}
+
+#endif
diff --git a/include/views/SkOSWindow_Android.h b/include/views/SkOSWindow_Android.h
new file mode 100644
index 0000000000..38a4cf8f5e
--- /dev/null
+++ b/include/views/SkOSWindow_Android.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 Skia
+ *
+ * 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.
+ */
+
+#ifndef SkOSWindow_Android_DEFINED
+#define SkOSWindow_Android_DEFINED
+
+#include "SkWindow.h"
+#include "SkEvent.h"
+
+class SkOSWindow : public SkWindow {
+public:
+ SkOSWindow(void*) {}
+ ~SkOSWindow() {}
+ bool attachGL() { return false; }
+ void detachGL() {}
+ void presentGL() {}
+
+protected:
+ // overrides from SkWindow
+ virtual void onHandleInval(const SkIRect&);
+ virtual void onSetTitle(const char title[]);
+
+private:
+ typedef SkWindow INHERITED;
+};
+
+#endif
+
diff --git a/include/views/SkWindow.h b/include/views/SkWindow.h
index 7b37779668..fd4ce0a91c 100644
--- a/include/views/SkWindow.h
+++ b/include/views/SkWindow.h
@@ -111,6 +111,8 @@ private:
#include "SkOSWindow_Mac.h"
#elif defined(SK_BUILD_FOR_WIN)
#include "SkOSWindow_Win.h"
+#elif defined(ANDROID)
+ #include "SkOSWindow_Android.h"
#elif defined(SK_BUILD_FOR_UNIX)
#include "SkOSWindow_Unix.h"
#elif defined(SK_BUILD_FOR_SDL)