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

Unverified Commit a78ee276 authored by alperozturk's avatar alperozturk
Browse files

fix

parent ee34ff72
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -174,11 +174,13 @@

        <service
            android:name=".widget.singlenote.SingleNoteWidgetService"
            android:permission="android.permission.BIND_REMOTEVIEWS" />
            android:permission="android.permission.BIND_REMOTEVIEWS"
            android:exported="false"/>

        <service
            android:name=".widget.notelist.NoteListWidgetService"
            android:permission="android.permission.BIND_REMOTEVIEWS" />
            android:permission="android.permission.BIND_REMOTEVIEWS"
            android:exported="false"/>

        <service
            android:name=".quicksettings.NewNoteTileService"
+110 −93
Original line number Diff line number Diff line
/*
 * Nextcloud Notes - Android Client
 *
 * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
 * SPDX-FileCopyrightText: 2017-2025 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package it.niedermann.owncloud.notes.widget.notelist;

import static it.niedermann.owncloud.notes.shared.util.WidgetUtil.pendingIntentFlagCompat;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.widget.RemoteViews;

import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.edit.EditNoteActivity;
import it.niedermann.owncloud.notes.persistence.NotesRepository;
package it.niedermann.owncloud.notes.widget.notelist

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.widget.RemoteViews
import com.owncloud.android.lib.common.utils.Log_OC
import it.niedermann.owncloud.notes.R
import it.niedermann.owncloud.notes.edit.EditNoteActivity
import it.niedermann.owncloud.notes.persistence.NotesRepository
import it.niedermann.owncloud.notes.shared.util.WidgetUtil
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import androidx.core.net.toUri

class NoteListWidget : AppWidgetProvider() {
    private val executor: ExecutorService = Executors.newCachedThreadPool()

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        updateAppWidget(context, appWidgetManager, appWidgetIds)
    }

public class NoteListWidget extends AppWidgetProvider {
    private static final String TAG = NoteListWidget.class.getSimpleName();
    private final ExecutorService executor = Executors.newCachedThreadPool();
    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        val awm = AppWidgetManager.getInstance(context)

    static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
        final var repo = NotesRepository.getInstance(context);
        if (intent.action == null) {
            Log.w(TAG, "Intent action is null")
            return
        }

        RemoteViews views;
        if (intent.action != AppWidgetManager.ACTION_APPWIDGET_UPDATE) {
            Log.w(TAG, "Intent action is not ACTION_APPWIDGET_UPDATE")
            return
        }

        for (int appWidgetId : appWidgetIds) {
            try {
                final var data = repo.getNoteListWidgetData(appWidgetId);
        if (!intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
            Log.w(TAG, "Update widget via default appWidgetIds")
            updateAppWidget(
                context,
                awm,
                awm.getAppWidgetIds(ComponentName(context, NoteListWidget::class.java))
            )
        }

                final var serviceIntent = new Intent(context, NoteListWidgetService.class);
                serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
        if (intent.extras == null) {
            Log.w(TAG, "Intent doesn't have bundle")
            return
        }

                Log.v(TAG, "-- data - " + data);
        Log.w(TAG, "Update widget via given appWidgetIds")

                Intent editNoteIntent = new Intent(context, EditNoteActivity.class);
                editNoteIntent.setPackage(context.getPackageName());
        val appWidgetIds = intArrayOf(intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) ?: -1)

                int pendingIntentFlags = pendingIntentFlagCompat(PendingIntent.FLAG_UPDATE_CURRENT | Intent.FILL_IN_COMPONENT);
                PendingIntent editNotePendingIntent = PendingIntent.getActivity(context, 0, editNoteIntent, pendingIntentFlags);
        updateAppWidget(
            context,
            awm,
            appWidgetIds
        )
    }

                views = new RemoteViews(context.getPackageName(), R.layout.widget_note_list);
                views.setRemoteAdapter(R.id.note_list_widget_lv, serviceIntent);
                views.setPendingIntentTemplate(R.id.note_list_widget_lv, editNotePendingIntent);
                views.setEmptyView(R.id.note_list_widget_lv, R.id.widget_note_list_placeholder_tv);
    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
        super.onDeleted(context, appWidgetIds)
        val repo = NotesRepository.getInstance(context)

                awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.note_list_widget_lv);
                awm.updateAppWidget(appWidgetId, views);
            } catch (NoSuchElementException e) {
                Log.i(TAG, "onUpdate has been triggered before the user finished configuring the widget");
            }
        for (appWidgetId in appWidgetIds) {
            executor.submit(Runnable { repo.removeNoteListWidget(appWidgetId) })
        }
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        updateAppWidget(context, appWidgetManager, appWidgetIds);
    companion object {
        private val TAG: String = NoteListWidget::class.java.getSimpleName()
        fun updateAppWidget(context: Context, awm: AppWidgetManager, appWidgetIds: IntArray) {
            val repo = NotesRepository.getInstance(context)
            appWidgetIds.forEach { appWidgetId ->
                repo.getNoteListWidgetData(appWidgetId)?.let { data ->
                    val serviceIntent = Intent(context, NoteListWidgetService::class.java).apply {
                        putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                        setData(toUri(Intent.URI_INTENT_SCHEME).toUri())
                    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        final var awm = AppWidgetManager.getInstance(context);

        if (intent.getAction() == null) {
            Log.w(TAG, "Intent action is null");
            return;
        }
                    Log.v(TAG, "-- data - $data")

        if (!intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE)) {
            Log.w(TAG, "Intent action is not ACTION_APPWIDGET_UPDATE");
            return;
                    val editNoteIntent = Intent(context, EditNoteActivity::class.java).apply {
                        setPackage(context.packageName)
                    }

        if (!intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
            Log.w(TAG,"Update widget via default appWidgetIds");
            updateAppWidget(context, awm, awm.getAppWidgetIds(new ComponentName(context, NoteListWidget.class)));
                    val pendingIntentFlags =
                        WidgetUtil.pendingIntentFlagCompat(PendingIntent.FLAG_UPDATE_CURRENT or Intent.FILL_IN_COMPONENT)
                    val editNotePendingIntent =
                        PendingIntent.getActivity(context, 0, editNoteIntent, pendingIntentFlags)

                    val views = RemoteViews(context.packageName, R.layout.widget_note_list).apply {
                        setRemoteAdapter(R.id.note_list_widget_lv, serviceIntent)
                        setPendingIntentTemplate(R.id.note_list_widget_lv, editNotePendingIntent)
                        setEmptyView(
                            R.id.note_list_widget_lv,
                            R.id.widget_note_list_placeholder_tv
                        )
                    }

        if (intent.getExtras() == null) {
            Log.w(TAG, "Intent doesn't have bundle");
            return;
                    awm.run {
                        updateAppWidget(appWidgetId, views)
                        notifyAppWidgetViewDataChanged(appWidgetId, R.id.note_list_widget_lv)
                    }

        Log.w(TAG,"Update widget via given appWidgetIds");
        updateAppWidget(context, awm, new int[]{intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)});
                }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        super.onDeleted(context, appWidgetIds);
        final var repo = NotesRepository.getInstance(context);

        for (final int appWidgetId : appWidgetIds) {
            executor.submit(() -> repo.removeNoteListWidget(appWidgetId));
            }
        }

    /**
     * Update note list widgets, if the note data was changed.
     */
    public static void updateNoteListWidgets(Context context) {
        context.sendBroadcast(new Intent(context, NoteListWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
        @JvmStatic
        fun updateNoteListWidgets(context: Context) {
            val intent = Intent(context, NoteListWidget::class.java).apply {
                setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
            }
            context.sendBroadcast(intent)
        }
    }
}
+30 −97
Original line number Diff line number Diff line
@@ -7,25 +7,21 @@
package it.niedermann.owncloud.notes.widget.notelist

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.RemoteViews
import android.widget.RemoteViewsService.RemoteViewsFactory
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import it.niedermann.owncloud.notes.R
import it.niedermann.owncloud.notes.edit.EditNoteActivity
import it.niedermann.owncloud.notes.main.MainActivity
import it.niedermann.owncloud.notes.persistence.NotesRepository
import it.niedermann.owncloud.notes.persistence.entity.Account
import it.niedermann.owncloud.notes.persistence.entity.Note
import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData
import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType
import it.niedermann.owncloud.notes.shared.model.NavigationCategory
import it.niedermann.owncloud.notes.shared.util.NotesColorUtil
import androidx.core.net.toUri

class NoteListWidgetFactory internal constructor(private val context: Context, intent: Intent) :
    RemoteViewsFactory {
@@ -37,9 +33,7 @@ class NoteListWidgetFactory internal constructor(private val context: Context, i
    private val dbNotes: MutableList<Note> = ArrayList()
    private var data: NotesListWidgetData? = null

    override fun onCreate() {
        // Nothing to do here…
    }
    override fun onCreate() = Unit

    override fun onDataSetChanged() {
        dbNotes.clear()
@@ -90,12 +84,10 @@ class NoteListWidgetFactory internal constructor(private val context: Context, i
        }
    }

    override fun onDestroy() {
        //NoOp
    }
    override fun onDestroy() = Unit

    override fun getCount(): Int {
        return dbNotes.size + 1
        return dbNotes.size
    }

    private fun getEditNoteIntent(bundle: Bundle): Intent {
@@ -130,74 +122,41 @@ class NoteListWidgetFactory internal constructor(private val context: Context, i
        return getEditNoteIntent(bundle)
    }

    private fun getRemoteViewFromData(): RemoteViews? {
        val widgetData = data ?: return null
    override fun getViewAt(position: Int): RemoteViews? {
        val note = dbNotes.getOrNull(position) ?: return null

        val localAccount = repo.getAccountById(widgetData.accountId)
        val createNoteIntent = getCreateNoteIntent(localAccount)
        val openIntent = Intent(Intent.ACTION_MAIN).setComponent(
            ComponentName(
                context.packageName,
                MainActivity::class.java.getName()
            )
        )
        val openNoteIntent = getOpenNoteIntent(note)

        return RemoteViews(context.packageName, R.layout.widget_entry_add).apply {
            setOnClickFillInIntent(R.id.widget_entry_content_tv, openIntent)
            setOnClickFillInIntent(R.id.widget_entry_fav_icon, createNoteIntent)
            setTextViewText(
                R.id.widget_entry_content_tv,
                getCategoryTitle(context, widgetData.mode, widgetData.category)
            )
            setImageViewResource(
                R.id.widget_entry_fav_icon,
                R.drawable.ic_add_blue_24dp
            )
            setInt(
                R.id.widget_entry_fav_icon,
                "setColorFilter",
                if (NotesColorUtil.contrastRatioIsSufficient(
                        ContextCompat.getColor(
                            context,
                            R.color.widget_background
                        ), localAccount.color
                    )
                )
                    localAccount.color
                else
                    ContextCompat.getColor(context, R.color.widget_foreground)
        return RemoteViews(context.packageName, R.layout.widget_entry).apply {
            setOnClickFillInIntent(R.id.widget_note_list_entry, openNoteIntent)
            setTextViewText(R.id.widget_entry_content_tv,
                (note.title + getCategoryTitle(context))
            )

            val starIconId = if (note.favorite) {
                R.drawable.ic_star_yellow_24dp
            } else {
                R.drawable.ic_star_grey_ccc_24dp
            }
            setImageViewResource(R.id.widget_entry_fav_icon, starIconId)
        }
    }

    private fun getRemoteViewFromPosition(position: Int): RemoteViews? {
        var position = position
        position--
        if (position < 0 || position >= dbNotes.size) {
            Log.e(TAG, "Could not find position \"$position\" in dbNotes list.")
    private fun getCategoryTitle(context: Context): String? {
        if (data == null) {
            return null
        }

        val note = dbNotes[position]
        val openNoteIntent = getOpenNoteIntent(note)
        val widgetData = data ?: return null

        return RemoteViews(context.packageName, R.layout.widget_entry).apply {
            setOnClickFillInIntent(R.id.widget_note_list_entry, openNoteIntent)
            setTextViewText(R.id.widget_entry_content_tv, note.title)
            setImageViewResource(
                R.id.widget_entry_fav_icon, if (note.favorite)
                    R.drawable.ic_star_yellow_24dp
        return when (widgetData.mode) {
            NotesListWidgetData.MODE_DISPLAY_STARRED -> context.getString(R.string.label_favorites)
            NotesListWidgetData.MODE_DISPLAY_CATEGORY -> if ("" == widgetData.category)
                context.getString(R.string.action_uncategorized)
            else
                    R.drawable.ic_star_grey_ccc_24dp
            )
        }
    }
                widgetData.category

    override fun getViewAt(position: Int): RemoteViews? {
        return if (position == 0 && data != null) {
            getRemoteViewFromData()
        } else {
            getRemoteViewFromPosition(position)
            else -> context.getString(R.string.app_name)
        }
    }

@@ -210,18 +169,8 @@ class NoteListWidgetFactory internal constructor(private val context: Context, i
    }

    override fun getItemId(position: Int): Long {
        var position = position
        if (position == 0) {
            return -1
        } else {
            position--
            if (position > dbNotes.size - 1 || dbNotes.get(position) == null) {
                Log.e(TAG, "Could not find position \"" + position + "\" in dbNotes list.")
                return -2
            }
        return dbNotes[position].id
    }
    }

    override fun hasStableIds(): Boolean {
        return true
@@ -229,21 +178,5 @@ class NoteListWidgetFactory internal constructor(private val context: Context, i

    companion object {
        private val TAG: String = NoteListWidgetFactory::class.java.getSimpleName()

        private fun getCategoryTitle(
            context: Context,
            displayMode: Int,
            category: String?
        ): String {
            return when (displayMode) {
                NotesListWidgetData.MODE_DISPLAY_STARRED -> context.getString(R.string.label_favorites)
                NotesListWidgetData.MODE_DISPLAY_CATEGORY -> if ("" == category)
                    context.getString(R.string.action_uncategorized)
                else
                    category

                else -> context.getString(R.string.app_name)
            }!!
        }
    }
}
+84 −76
Original line number Diff line number Diff line
@@ -4,90 +4,98 @@
 * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package it.niedermann.owncloud.notes.widget.singlenote;

import static it.niedermann.owncloud.notes.shared.util.WidgetUtil.pendingIntentFlagCompat;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.widget.RemoteViews;
package it.niedermann.owncloud.notes.widget.singlenote

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import it.niedermann.owncloud.notes.R
import it.niedermann.owncloud.notes.edit.BaseNoteFragment
import it.niedermann.owncloud.notes.edit.EditNoteActivity
import it.niedermann.owncloud.notes.persistence.NotesRepository
import it.niedermann.owncloud.notes.shared.util.WidgetUtil
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import androidx.core.net.toUri

class SingleNoteWidget : AppWidgetProvider() {
    private val executor: ExecutorService = Executors.newCachedThreadPool()

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        updateAppWidget(context, appWidgetManager, appWidgetIds)
    }

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
    override fun onReceive(context: Context, intent: Intent?) {
        super.onReceive(context, intent)
        val awm = AppWidgetManager.getInstance(context)

import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.edit.BaseNoteFragment;
import it.niedermann.owncloud.notes.edit.EditNoteActivity;
import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData;
        val provider = ComponentName(context, SingleNoteWidget::class.java)
        val appWidgetIds = awm.getAppWidgetIds(provider)
        updateAppWidget(context, awm, appWidgetIds)
    }

public class SingleNoteWidget extends AppWidgetProvider {
    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
        val repo = NotesRepository.getInstance(context)

    private static final String TAG = SingleNoteWidget.class.getSimpleName();
    private final ExecutorService executor = Executors.newCachedThreadPool();
        for (appWidgetId in appWidgetIds) {
            executor.submit { repo.removeSingleNoteWidget(appWidgetId) }
        }
        super.onDeleted(context, appWidgetIds)
    }

    static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
        final var templateIntent = new Intent(context, EditNoteActivity.class);
        final var repo = NotesRepository.getInstance(context);
    companion object {
        private val TAG: String = SingleNoteWidget::class.java.getSimpleName()
        fun updateAppWidget(context: Context, awm: AppWidgetManager, appWidgetIds: IntArray) {
            val templateIntent = Intent(context, EditNoteActivity::class.java)
            val repo = NotesRepository.getInstance(context)

        for (int appWidgetId : appWidgetIds) {
            final var data = repo.getSingleNoteWidgetData(appWidgetId);
            if (data != null) {
                templateIntent.putExtra(BaseNoteFragment.PARAM_ACCOUNT_ID, data.getAccountId());
            appWidgetIds.forEach { appWidgetId ->
                repo.getSingleNoteWidgetData(appWidgetId)?.let { data ->
                    templateIntent.putExtra(BaseNoteFragment.PARAM_ACCOUNT_ID, data.accountId)

                final var serviceIntent = new Intent(context, SingleNoteWidgetService.class);
                serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
                    val serviceIntent = Intent(context, SingleNoteWidgetService::class.java).apply {
                        putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                        setData(toUri(Intent.URI_INTENT_SCHEME).toUri())
                    }

                final var views = new RemoteViews(context.getPackageName(), R.layout.widget_single_note);
                views.setPendingIntentTemplate(R.id.single_note_widget_lv, PendingIntent.getActivity(context, appWidgetId, templateIntent,
                        pendingIntentFlagCompat(PendingIntent.FLAG_UPDATE_CURRENT)));
                views.setRemoteAdapter(R.id.single_note_widget_lv, serviceIntent);
                views.setEmptyView(R.id.single_note_widget_lv, R.id.widget_single_note_placeholder_tv);

                awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.single_note_widget_lv);
                awm.updateAppWidget(appWidgetId, views);
            } else {
                Log.i(TAG, "onUpdate has been triggered before the user finished configuring the widget");
                    val views = RemoteViews(context.packageName, R.layout.widget_single_note).apply {
                        setPendingIntentTemplate(
                            R.id.single_note_widget_lv, PendingIntent.getActivity(
                                context, appWidgetId, templateIntent,
                                WidgetUtil.pendingIntentFlagCompat(PendingIntent.FLAG_UPDATE_CURRENT)
                            )
                        )
                        setRemoteAdapter(R.id.single_note_widget_lv, serviceIntent)
                        setEmptyView(
                            R.id.single_note_widget_lv,
                            R.id.widget_single_note_placeholder_tv
                        )
                    }

                    awm.run {
                        updateAppWidget(appWidgetId, views)
                        notifyAppWidgetViewDataChanged(appWidgetId, R.id.single_note_widget_lv)
                    }
                }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        updateAppWidget(context, appWidgetManager, appWidgetIds);
            }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        final var awm = AppWidgetManager.getInstance(context);

        updateAppWidget(context, AppWidgetManager.getInstance(context),
                (awm.getAppWidgetIds(new ComponentName(context, SingleNoteWidget.class))));
        }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        final var repo = NotesRepository.getInstance(context);

        for (int appWidgetId : appWidgetIds) {
            executor.submit(() -> repo.removeSingleNoteWidget(appWidgetId));
        @JvmStatic
        fun updateSingleNoteWidgets(context: Context) {
            val intent = Intent(context, SingleNoteWidget::class.java).apply {
                setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
            }
        super.onDeleted(context, appWidgetIds);
            context.sendBroadcast(intent)
        }

    /**
     * Update single note widget, if the note data was changed.
     */
    public static void updateSingleNoteWidgets(Context context) {
        context.sendBroadcast(new Intent(context, SingleNoteWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
    }
}
+1 −1

File changed.

Preview size limit exceeded, changes collapsed.