core/java/android/print/IPrintDocumentAdapter.aidl \
core/java/android/print/IPrintDocumentAdapterObserver.aidl \
core/java/android/print/IPrintJobStateChangeListener.aidl \
+ core/java/android/print/IPrintServicesChangeListener.aidl \
core/java/android/print/IPrintManager.aidl \
core/java/android/print/IPrintSpooler.aidl \
core/java/android/print/IPrintSpoolerCallbacks.aidl \
field public static final int ERROR_NO_KEY = 1; // 0x1
field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3
field public static final int ERROR_SESSION_NOT_OPENED = 5; // 0x5
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 6; // 0x6
}
public static final class MediaCodec.CryptoInfo {
field public static final int ERROR_NO_KEY = 1; // 0x1
field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3
field public static final int ERROR_SESSION_NOT_OPENED = 5; // 0x5
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 6; // 0x6
}
public static final class MediaCodec.CryptoInfo {
field public static final int ERROR_NO_KEY = 1; // 0x1
field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3
field public static final int ERROR_SESSION_NOT_OPENED = 5; // 0x5
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 6; // 0x6
}
public static final class MediaCodec.CryptoInfo {
package android.print;
+import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.print.IPrinterDiscoveryObserver;
import android.print.IPrintDocumentAdapter;
import android.print.PrintJobId;
import android.print.IPrintJobStateChangeListener;
+import android.print.IPrintServicesChangeListener;
import android.print.PrinterId;
import android.print.PrintJobInfo;
import android.print.PrintAttributes;
void removePrintJobStateChangeListener(in IPrintJobStateChangeListener listener,
int userId);
- List<PrintServiceInfo> getInstalledPrintServices(int userId);
- List<PrintServiceInfo> getEnabledPrintServices(int userId);
+ /**
+ * Listen for changes to the installed and enabled print services.
+ *
+ * @param listener the listener to add
+ * @param userId the id of the user listening
+ *
+ * @see android.print.PrintManager#getPrintServices(int, String)
+ */
+ void addPrintServicesChangeListener(in IPrintServicesChangeListener listener,
+ int userId);
+
+ /**
+ * Stop listening for changes to the installed and enabled print services.
+ *
+ * @param listener the listener to remove
+ * @param userId the id of the user requesting the removal
+ *
+ * @see android.print.PrintManager#getPrintServices(int, String)
+ */
+ void removePrintServicesChangeListener(in IPrintServicesChangeListener listener,
+ int userId);
+
+ /**
+ * Get the print services.
+ *
+ * @param selectionFlags flags selecting which services to get
+ * @param selectedService if not null, the id of the print service to get
+ * @param userId the id of the user requesting the services
+ *
+ * @return the list of selected print services.
+ */
+ List<PrintServiceInfo> getPrintServices(int selectionFlags, int userId);
+
+ /**
+ * Enable or disable a print service.
+ *
+ * @param service The service to enabled or disable
+ * @param isEnabled whether the service should be enabled or disabled
+ * @param userId the id of the user requesting the services
+ */
+ void setPrintServiceEnabled(in ComponentName service, boolean isEnabled, int userId);
void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId);
void startPrinterDiscovery(in IPrinterDiscoveryObserver observer,
--- /dev/null
+/*
+ * 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 android.print;
+
+/**
+ * Interface for observing print services changes.
+ *
+ * @hide
+ */
+oneway interface IPrintServicesChangeListener {
+ void onPrintServicesChanged();
+}
import android.annotation.Nullable;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
+import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
private static final boolean DEBUG = false;
private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
+ private static final int MSG_NOTIFY_PRINT_SERVICES_CHANGED = 2;
+
+ /**
+ * Package name of print spooler.
+ *
+ * @hide
+ */
+ public static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
+
+ /**
+ * Select enabled services.
+ * </p>
+ * @see #getPrintServices
+ * @hide
+ */
+ public static final int ENABLED_SERVICES = 1 << 0;
+
+ /**
+ * Select disabled services.
+ * </p>
+ * @see #getPrintServices
+ * @hide
+ */
+ public static final int DISABLED_SERVICES = 1 << 1;
+
+ /**
+ * Select all services.
+ * </p>
+ * @see #getPrintServices
+ * @hide
+ */
+ public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES;
/**
* The action for launching the print dialog activity.
private final Handler mHandler;
- private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
+ private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper>
+ mPrintJobStateChangeListeners;
+ private Map<PrintServicesChangeListener, PrintServicesChangeListenerWrapper>
+ mPrintServicesChangeListeners;
/** @hide */
public interface PrintJobStateChangeListener {
public void onPrintJobStateChanged(PrintJobId printJobId);
}
+ /** @hide */
+ public interface PrintServicesChangeListener {
+
+ /**
+ * Callback notifying that the print services changed.
+ */
+ public void onPrintServicesChanged();
+ }
+
/**
* Creates a new instance.
*
}
args.recycle();
} break;
+ case MSG_NOTIFY_PRINT_SERVICES_CHANGED: {
+ PrintServicesChangeListenerWrapper wrapper =
+ (PrintServicesChangeListenerWrapper) message.obj;
+ PrintServicesChangeListener listener = wrapper.getListener();
+ if (listener != null) {
+ listener.onPrintServicesChanged();
+ }
+ } break;
+
}
}
};
}
/**
- * Gets the list of enabled print services.
+ * Listen for changes to the installed and enabled print services.
*
- * @return The enabled service list or an empty list.
- * @hide
+ * @param listener the listener to add
+ *
+ * @see android.print.PrintManager#getPrintServices
*/
- public List<PrintServiceInfo> getEnabledPrintServices() {
+ void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) {
if (mService == null) {
Log.w(LOG_TAG, "Feature android.software.print not available");
- return Collections.emptyList();
+ return;
}
+ if (mPrintServicesChangeListeners == null) {
+ mPrintServicesChangeListeners = new ArrayMap<PrintServicesChangeListener,
+ PrintServicesChangeListenerWrapper>();
+ }
+ PrintServicesChangeListenerWrapper wrappedListener =
+ new PrintServicesChangeListenerWrapper(listener, mHandler);
try {
- List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId);
- if (enabledServices != null) {
- return enabledServices;
- }
+ mService.addPrintServicesChangeListener(wrappedListener, mUserId);
+ mPrintServicesChangeListeners.put(listener, wrappedListener);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
- return Collections.emptyList();
}
/**
- * Gets the list of installed print services.
+ * Stop listening for changes to the installed and enabled print services.
*
- * @return The installed service list or an empty list.
- * @hide
+ * @param listener the listener to remove
+ *
+ * @see android.print.PrintManager#getPrintServices
*/
- public List<PrintServiceInfo> getInstalledPrintServices() {
+ void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) {
if (mService == null) {
Log.w(LOG_TAG, "Feature android.software.print not available");
- return Collections.emptyList();
+ return;
+ }
+ if (mPrintServicesChangeListeners == null) {
+ return;
}
+ PrintServicesChangeListenerWrapper wrappedListener =
+ mPrintServicesChangeListeners.remove(listener);
+ if (wrappedListener == null) {
+ return;
+ }
+ if (mPrintServicesChangeListeners.isEmpty()) {
+ mPrintServicesChangeListeners = null;
+ }
+ wrappedListener.destroy();
try {
- List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
- if (installedServices != null) {
- return installedServices;
+ mService.removePrintServicesChangeListener(wrappedListener, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error removing print services change listener", re);
+ }
+ }
+
+
+ /**
+ * Gets the list of print services, but does not register for updates. The user has to register
+ * for updates by itself, or use {@link PrintServicesLoader}.
+ *
+ * @param selectionFlags flags selecting which services to get. Either
+ * {@link #ENABLED_SERVICES},{@link #DISABLED_SERVICES}, or both.
+ *
+ * @return The enabled service list or an empty list.
+ *
+ * @see #addPrintServicesChangeListener(PrintServicesChangeListener)
+ * @see #removePrintServicesChangeListener(PrintServicesChangeListener)
+ *
+ * @hide
+ */
+ public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) {
+ try {
+ List<PrintServiceInfo> services = mService.getPrintServices(selectionFlags, mUserId);
+ if (services != null) {
+ return services;
}
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
/**
+ * Enable or disable a print service.
+ *
+ * @param service The service to enabled or disable
+ * @param isEnabled whether the service should be enabled or disabled
+ *
+ * @hide
+ */
+ public void setPrintServiceEnabled(@NonNull ComponentName service, boolean isEnabled) {
+ if (mService == null) {
+ Log.w(LOG_TAG, "Feature android.software.print not available");
+ return;
+ }
+ try {
+ mService.setPrintServiceEnabled(service, isEnabled, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error enabling or disabling " + service, re);
+ }
+ }
+
+ /**
* @hide
*/
public static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
return mWeakListener.get();
}
}
+
+ /**
+ * @hide
+ */
+ public static final class PrintServicesChangeListenerWrapper extends
+ IPrintServicesChangeListener.Stub {
+ private final WeakReference<PrintServicesChangeListener> mWeakListener;
+ private final WeakReference<Handler> mWeakHandler;
+
+ public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener,
+ Handler handler) {
+ mWeakListener = new WeakReference<>(listener);
+ mWeakHandler = new WeakReference<>(handler);
+ }
+
+ @Override
+ public void onPrintServicesChanged() {
+ Handler handler = mWeakHandler.get();
+ PrintServicesChangeListener listener = mWeakListener.get();
+ if (handler != null && listener != null) {
+ handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICES_CHANGED, this).sendToTarget();
+ }
+ }
+
+ public void destroy() {
+ mWeakListener.clear();
+ }
+
+ public PrintServicesChangeListener getListener() {
+ return mWeakListener.get();
+ }
+ }
}
--- /dev/null
+/*
+ * 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 android.print;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Handler;
+import android.os.Message;
+import android.printservice.PrintServiceInfo;
+
+import java.util.List;
+
+/**
+ * Loader for the list of print services. Can be parametrized to select a subset.
+ *
+ * @hide
+ */
+public class PrintServicesLoader extends Loader<List<PrintServiceInfo>> {
+ /** What type of services to load. */
+ private final int mSelectionFlags;
+
+ /** The print manager to be used by this object */
+ private final @NonNull PrintManager mPrintManager;
+
+ /** Handler to sequentialize the delivery of the results to the main thread */
+ private Handler mHandler;
+
+ /** Listens for updates to the data from the platform */
+ private PrintManager.PrintServicesChangeListener mListener;
+
+ /**
+ * Create a new PrintServicesLoader.
+ *
+ * @param selectionFlags What type of services to load.
+ */
+ public PrintServicesLoader(@NonNull PrintManager printManager, @NonNull Context context,
+ int selectionFlags) {
+ super(context);
+ mPrintManager = printManager;
+ mSelectionFlags = selectionFlags;
+ }
+
+ @Override
+ protected void onForceLoad() {
+ queueNewResult();
+ }
+
+ /**
+ * Read the print services and queue it to be delivered on the main thread.
+ */
+ private void queueNewResult() {
+ Message m = mHandler.obtainMessage(0);
+ m.obj = mPrintManager.getPrintServices(mSelectionFlags);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ mHandler = new MyHandler();
+ mListener = new PrintManager.PrintServicesChangeListener() {
+ @Override public void onPrintServicesChanged() {
+ queueNewResult();
+ }
+ };
+
+ mPrintManager.addPrintServicesChangeListener(mListener);
+
+ // Immediately deliver a result
+ deliverResult(mPrintManager.getPrintServices(mSelectionFlags));
+ }
+
+ @Override
+ protected void onStopLoading() {
+ if (mListener != null) {
+ mPrintManager.removePrintServicesChangeListener(mListener);
+ mListener = null;
+ }
+
+ if (mHandler != null) {
+ mHandler.removeMessages(0);
+ mHandler = null;
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ onStopLoading();
+ }
+
+ /**
+ * Handler to sequentialize all the updates to the main thread.
+ */
+ private class MyHandler extends Handler {
+ /**
+ * Create a new handler on the main thread.
+ */
+ public MyHandler() {
+ super(getContext().getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+
+ if (isStarted()) {
+ deliverResult((List<PrintServiceInfo>) msg.obj);
+ }
+ }
+ }
+}
private final String mId;
+ private boolean mIsEnabled;
+
private final ResolveInfo mResolveInfo;
private final String mSettingsActivityName;
*/
public PrintServiceInfo(Parcel parcel) {
mId = parcel.readString();
+ mIsEnabled = parcel.readByte() != 0;
mResolveInfo = parcel.readParcelable(null);
mSettingsActivityName = parcel.readString();
mAddPrintersActivityName = parcel.readString();
}
/**
+ * If the service was enabled when it was read from the system.
+ *
+ * @return The id.
+ */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /**
+ * Mark a service as enabled or not
+ *
+ * @param isEnabled If the service should be marked as enabled.
+ */
+ public void setIsEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ }
+
+ /**
* The service {@link ResolveInfo}.
*
* @return The info.
@Override
public void writeToParcel(Parcel parcel, int flagz) {
parcel.writeString(mId);
+ parcel.writeByte((byte)(mIsEnabled ? 1 : 0));
parcel.writeParcelable(mResolveInfo, 0);
parcel.writeString(mSettingsActivityName);
parcel.writeString(mAddPrintersActivityName);
StringBuilder builder = new StringBuilder();
builder.append("PrintServiceInfo{");
builder.append("id=").append(mId);
+ builder.append("isEnabled=").append(mIsEnabled);
builder.append(", resolveInfo=").append(mResolveInfo);
builder.append(", settingsActivityName=").append(mSettingsActivityName);
builder.append(", addPrintersActivityName=").append(mAddPrintersActivityName);
// to executing this method, so we can rely on that instead.
final Transition exitTransition = mExitTransition;
if (mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
- // The decor view is non-interactive during exit transitions.
+ // The decor view is non-interactive and non-IME-focusable during exit transitions.
final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+ p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
mWindowManager.updateViewLayout(decorView, p);
// Once we start dismissing the decor view, all state (including
import com.android.internal.annotations.VisibleForTesting;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.text.TextUtils;
+import android.icu.util.ULocale;
import android.util.LocaleList;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Objects;
public final class LocaleUtils {
Locale get(@Nullable T source);
}
- @Nullable
- private static String getLanguage(@Nullable Locale locale) {
- if (locale == null) {
- return null;
+ /**
+ * Calculates a matching score for the single desired locale.
+ *
+ * @see LocaleUtils#calculateMatchingScore(ULocale, LocaleList, byte[])
+ *
+ * @param supported The locale supported by IME subtype.
+ * @param desired The locale preferred by user.
+ * @return A score based on the locale matching for the default subtype enabling.
+ */
+ @IntRange(from=1, to=3)
+ private static byte calculateMatchingSubScore(@NonNull final ULocale supported,
+ @NonNull final ULocale desired) {
+ // Assuming supported/desired is fully expanded.
+ if (supported.equals(desired)) {
+ return 3; // Exact match.
+ }
+
+ // Skip language matching since it was already done in calculateMatchingScore.
+
+ final String supportedScript = supported.getScript();
+ if (supportedScript.isEmpty() || !supportedScript.equals(desired.getScript())) {
+ // TODO: Need subscript matching. For example, Hanb should match with Bopo.
+ return 1;
+ }
+
+ final String supportedCountry = supported.getCountry();
+ if (supportedCountry.isEmpty() || !supportedCountry.equals(desired.getCountry())) {
+ return 2;
+ }
+
+ // Ignore others e.g. variants, extensions.
+ return 3;
+ }
+
+ /**
+ * Calculates a matching score for the desired locale list.
+ *
+ * <p>The supported locale gets a matching score of 3 if all language, script and country of the
+ * supported locale matches with the desired locale. The supported locale gets a matching
+ * score of 2 if the language and script of the supported locale matches with the desired
+ * locale. The supported locale gets a matching score of 1 if only language of the supported
+ * locale matches with the desired locale. The supported locale gets a matching score of 0 if
+ * the language of the supported locale doesn't match with the desired locale.</p>
+ *
+ * @param supported The locale supported by IME subtyle.
+ * @param desired The locale list preferred by user. Typically system locale list.
+ * @param out The output buffer to be stored the individual score for the desired language list.
+ * The length of {@code out} must be same as the length of {@code desired} language list.
+ * @return {@code false} if supported locale doesn't match with any desired locale list.
+ * Otherwise {@code true}.
+ */
+ private static boolean calculateMatchingScore(@NonNull final ULocale supported,
+ @NonNull final LocaleList desired, @NonNull byte[] out) {
+ if (desired.isEmpty()) {
+ return false;
+ }
+
+ boolean allZeros = true;
+ final int N = desired.size();
+ for (int i = 0; i < N; ++i) {
+ final Locale locale = desired.get(i);
+
+ if (!locale.getLanguage().equals(supported.getLanguage())) {
+ // TODO: cache the result of addLikelySubtags if it is slow.
+ out[i] = 0;
+ } else {
+ out[i] = calculateMatchingSubScore(
+ supported, ULocale.addLikelySubtags(ULocale.forLocale(locale)));
+ if (allZeros && out[i] != 0) {
+ allZeros = false;
+ }
+ }
+ }
+ return !allZeros;
+ }
+
+ private static final class ScoreEntry implements Comparable<ScoreEntry> {
+ public int mIndex = -1;
+ @NonNull public final byte[] mScore; // matching score of the i-th system languages.
+
+ ScoreEntry(@NonNull byte[] score, int index) {
+ mScore = new byte[score.length];
+ set(score, index);
+ }
+
+ private void set(@NonNull byte[] score, int index) {
+ for (int i = 0; i < mScore.length; ++i) {
+ mScore[i] = score[i];
+ }
+ mIndex = index;
+ }
+
+ /**
+ * Update score and index if the given score is better than this.
+ */
+ public void updateIfBetter(@NonNull byte[] score, int index) {
+ if (compare(mScore, score) == -1) { // mScore < score
+ set(score, index);
+ }
+ }
+
+ /**
+ * Provides comaprison for bytes[].
+ *
+ * <p> Comparison does as follows. If the first value of {@code left} is larger than the
+ * first value of {@code right}, {@code left} is large than {@code right}. If the first
+ * value of {@code left} is less than the first value of {@code right}, {@code left} is less
+ * than {@code right}. If the first value of {@code left} and the first value of
+ * {@code right} is equal, do the same comparison to the next value. Finally if all values
+ * in {@code left} and {@code right} are equal, {@code left} and {@code right} is equal.</p>
+ *
+ * @param left The length must be equal to {@code right}.
+ * @param right The length must be equal to {@code left}.
+ * @return 1 if {@code left} is larger than {@code right}. -1 if {@code left} is less than
+ * {@code right}. 0 if {@code left} and {@code right} is equal.
+ */
+ @IntRange(from=-1, to=1)
+ private static int compare(@NonNull byte[] left, @NonNull byte[] right) {
+ for (int i = 0; i < left.length; ++i) {
+ if (left[i] > right[i]) {
+ return 1;
+ } else if (left[i] < right[i]) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int compareTo(final ScoreEntry other) {
+ return -1 * compare(mScore, other.mScore); // Order by descending order.
}
- return locale.getLanguage();
}
/**
* {@code "en-GB", "ja", "en-AU", "fr-CA", "en-IN"} is specified to {@code preferredLanguages},
* this method tries to copy at most one English locale, at most one Japanese, and at most one
* French locale from {@code source} to {@code dest}. Here the best matching English locale
- * will be searched from {@code source} as follows.
- * <ol>
- * <li>The first instance in {@code sources} that exactly matches {@code "en-GB"}</li>
- * <li>The first instance in {@code sources} that exactly matches {@code "en-AU"}</li>
- * <li>The first instance in {@code sources} that exactly matches {@code "en-IN"}</li>
- * <li>The first instance in {@code sources} that partially matches {@code "en"}</li>
- * </ol>
- * <p>Then this method iterates the same algorithm for Japanese then French.</p>
+ * will be searched from {@code source} based on matching score. For the score design, see
+ * {@link LocaleUtils#calculateMatchingScore(ULocale, LocaleList, byte[])}</p>
*
* @param sources Source items to be filtered.
* @param extractor Type converter from the source items to {@link Locale} object.
@NonNull LocaleExtractor<T> extractor,
@NonNull LocaleList preferredLanguages,
@NonNull ArrayList<T> dest) {
- final Locale[] availableLocales = new Locale[sources.size()];
- for (int i = 0; i < availableLocales.length; ++i) {
- availableLocales[i] = extractor.get(sources.get(i));
- }
- final Locale[] sortedPreferredLanguages = new Locale[preferredLanguages.size()];
- if (sortedPreferredLanguages.length > 0) {
- int nextIndex = 0;
- final int N = preferredLanguages.size();
- languageLoop:
- for (int i = 0; i < N; ++i) {
- final String language = getLanguage(preferredLanguages.get(i));
- for (int j = 0; j < nextIndex; ++j) {
- if (TextUtils.equals(getLanguage(sortedPreferredLanguages[j]), language)) {
- continue languageLoop;
- }
- }
- for (int j = i; j < N; ++j) {
- final Locale locale = preferredLanguages.get(j);
- if (TextUtils.equals(language, getLanguage(locale))) {
- sortedPreferredLanguages[nextIndex] = locale;
- ++nextIndex;
- }
- }
- }
- }
+ final HashMap<String, ScoreEntry> scoreboard = new HashMap<>();
+ final byte[] score = new byte[preferredLanguages.size()];
-
- for (int languageIndex = 0; languageIndex < sortedPreferredLanguages.length;) {
- // Finding the range.
- final String language = getLanguage(sortedPreferredLanguages[languageIndex]);
- int nextLanguageIndex = languageIndex;
- for (; nextLanguageIndex < sortedPreferredLanguages.length; ++nextLanguageIndex) {
- final Locale locale = sortedPreferredLanguages[nextLanguageIndex];
- if (!TextUtils.equals(getLanguage(locale), language)) {
- break;
- }
+ final int sourceSize = sources.size();
+ for (int i = 0; i < sourceSize; ++i) {
+ final Locale locale = extractor.get(sources.get(i));
+ if (locale == null ||
+ !calculateMatchingScore(ULocale.addLikelySubtags(ULocale.forLocale(locale)),
+ preferredLanguages, score)) {
+ continue;
}
- // Check exact match
- boolean found = false;
- for (int i = languageIndex; !found && i < nextLanguageIndex; ++i) {
- final Locale locale = sortedPreferredLanguages[i];
- for (int j = 0; j < availableLocales.length; ++j) {
- if (!Objects.equals(locale, availableLocales[j])) {
- continue;
- }
- dest.add(sources.get(j));
- found = true;
- break;
- }
+ final String lang = locale.getLanguage();
+ final ScoreEntry bestScore = scoreboard.get(lang);
+ if (bestScore == null) {
+ scoreboard.put(lang, new ScoreEntry(score, i));
+ } else {
+ bestScore.updateIfBetter(score, i);
}
+ }
- if (!found) {
- // No exact match. Use language match.
- for (int j = 0; j < availableLocales.length; ++j) {
- if (!TextUtils.equals(language, getLanguage(availableLocales[j]))) {
- continue;
- }
- dest.add(sources.get(j));
- break;
- }
- }
- languageIndex = nextLanguageIndex;
+ final ScoreEntry[] result = scoreboard.values().toArray(new ScoreEntry[scoreboard.size()]);
+ Arrays.sort(result);
+ for (final ScoreEntry entry : result) {
+ dest.add(sources.get(entry.mIndex));
}
}
-}
\ No newline at end of file
+}
static jint
android_glCreateShaderProgramv
(JNIEnv *_env, jobject _this, jint type, jobjectArray strings) {
+ jint _exception = 0;
+ const char * _exceptionType = NULL;
+ const char * _exceptionMessage = NULL;
+ GLsizei _count;
+ const GLchar** _strings = NULL;
+ jstring* _jstrings = NULL;
+ GLuint _returnValue = 0;
- jniThrowException(_env, "java/lang/UnsupportedOperationException", "not yet implemented");
- return 0;
+ if (!strings) {
+ _exception = 1;
+ _exceptionType = "java/lang/IllegalArgumentException";
+ _exceptionMessage = "strings == null";
+ goto exit;
+ }
+
+ _count = _env->GetArrayLength(strings);
+
+ _strings = (const GLchar**) calloc(_count, sizeof(const GLchar*));
+ if (!_strings) {
+ _exception = 1;
+ _exceptionType = "java/lang/OutOfMemoryError";
+ _exceptionMessage = "out of memory";
+ goto exit;
+ }
+
+ _jstrings = (jstring*) calloc(_count, sizeof(jstring));
+ if (!_jstrings) {
+ _exception = 1;
+ _exceptionType = "java/lang/OutOfMemoryError";
+ _exceptionMessage = "out of memory";
+ goto exit;
+ }
+
+ for(int i = 0; i < _count; i++) {
+ _jstrings[i] = (jstring) _env->GetObjectArrayElement(strings, i);
+ if (!_jstrings[i]) {
+ _exception = 1;
+ _exceptionType = "java/lang/IllegalArgumentException";
+ _exceptionMessage = "strings == null";
+ goto exit;
+ }
+ _strings[i] = _env->GetStringUTFChars(_jstrings[i], 0);
+ }
+
+ _returnValue = glCreateShaderProgramv((GLenum)type, _count, _strings);
+exit:
+ if (_strings && _jstrings) {
+ for(int i = 0; i < _count; i++) {
+ if (_strings[i] && _jstrings[i]) {
+ _env->ReleaseStringUTFChars(_jstrings[i], _strings[i]);
+ }
+ }
+ }
+ if (_strings) {
+ free(_strings);
+ }
+ if (_jstrings) {
+ free(_jstrings);
+ }
+ if (_exception) {
+ jniThrowException(_env, _exceptionType, _exceptionMessage);
+ }
+ return (jint)_returnValue;
}
/* void glBindProgramPipeline ( GLuint pipeline ) */
static void
public abstract class BasePrintTest extends InstrumentationTestCase {
private static final long OPERATION_TIMEOUT = 30000;
- private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
protected void clearPrintSpoolerData() throws Exception {
assertTrue("failed to clear print spooler data",
runShellCommand(getInstrumentation(), String.format(
- "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
- .contains(PM_CLEAR_SUCCESS_OUTPUT));
+ "pm clear --user %d %s", CURRENT_USER_ID,
+ PrintManager.PRINT_SPOOLER_PACKAGE_NAME))
+ .contains(PM_CLEAR_SUCCESS_OUTPUT));
}
@SuppressWarnings("unchecked")
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.print.IPrintDocumentAdapter;
-import android.print.IPrintJobStateChangeListener;
-import android.print.IPrintManager;
-import android.print.IPrinterDiscoveryObserver;
-import android.print.PageRange;
-import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintJob;
-import android.print.PrintJobId;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.print.PrinterCapabilitiesInfo;
-import android.print.PrinterDiscoverySession;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
import android.printservice.PrintServiceInfo;
import android.print.mockservice.MockPrintService;
if (session.getPrinters().isEmpty()) {
final String PRINTER_NAME = "good printer";
- List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ List<PrinterInfo> printers = new ArrayList<>();
// Add the printer.
mGoodPrinterId = session.getService()
}
/**
+ * Create a IPrintServicesChangeListener object.
+ *
+ * @return the object
+ * @throws Exception if the object could not be created.
+ */
+ private IPrintServicesChangeListener createMockIPrintServicesChangeListener() throws Exception {
+ return new PrintManager.PrintServicesChangeListenerWrapper(null,
+ new Handler(Looper.getMainLooper()));
+ }
+
+
+ /**
* Create a IPrinterDiscoveryObserver object.
*
* @return the object
return new PrinterDiscoverySession.PrinterDiscoveryObserver(null);
}
+ private void startPrinting() {
+ mGoodPrintJob = print(createMockAdapter(), null);
+
+ // Wait for PrintActivity to be ready
+ waitForStartAdapterCallbackCalled();
+
+ // Wait for printer discovery session to be ready
+ waitForPrinterDiscoverySessionStartCallbackCalled();
+ }
+
@Override
public void setUp() throws Exception {
super.setUp();
mGoodComponentName = getActivity().getComponentName();
- mGoodPrintJob = print(createMockAdapter(), null);
-
mIPrintManager = IPrintManager.Stub
.asInterface(ServiceManager.getService(Context.PRINT_SERVICE));
// Generate dummy printerId which is a valid PrinterId object, but does not correspond to a
// printer
mBadPrinterId = new PrinterId(mGoodComponentName, "dummy printer");
-
- // Wait for PrintActivity to be ready
- waitForStartAdapterCallbackCalled();
-
- // Wait for printer discovery session to be ready
- waitForPrinterDiscoverySessionStartCallbackCalled();
}
/**
*/
private interface Invokable {
/**
- * Execute the {@link Invokable}
+ * Execute the invokable
*
* @throws Exception
*/
- public void run() throws Exception;
+ void run() throws Exception;
}
/**
* test IPrintManager.getPrintJobInfo
*/
public void testGetPrintJobInfo() throws Exception {
+ startPrinting();
+
assertEquals(mGoodPrintJob.getId(), mIPrintManager.getPrintJobInfo(mGoodPrintJob.getId(),
mAppId, mUserId).getId());
assertEquals(null, mIPrintManager.getPrintJobInfo(mBadPrintJobId, mAppId, mUserId));
* test IPrintManager.getPrintJobInfos
*/
public void testGetPrintJobInfos() throws Exception {
+ startPrinting();
+
List<PrintJobInfo> infos = mIPrintManager.getPrintJobInfos(mAppId, mUserId);
boolean foundPrintJob = false;
final IPrintDocumentAdapter adapter = new PrintManager
.PrintDocumentAdapterDelegate(getActivity(), createMockAdapter());
- // Valid parameters are tested in setUp()
+ startPrinting();
assertException(new Invokable() {
@Override
* test IPrintManager.cancelPrintJob
*/
public void testCancelPrintJob() throws Exception {
+ startPrinting();
+
// Invalid print jobs IDs do not produce an exception
mIPrintManager.cancelPrintJob(mBadPrintJobId, mAppId, mUserId);
mIPrintManager.cancelPrintJob(null, mAppId, mUserId);
* test IPrintManager.restartPrintJob
*/
public void testRestartPrintJob() throws Exception {
+ startPrinting();
+
mIPrintManager.restartPrintJob(mGoodPrintJob.getId(), mAppId, mUserId);
// Invalid print jobs IDs do not produce an exception
}
/**
- * test IPrintManager.getInstalledPrintServices
+ * test IPrintManager.addPrintServicesChangeListener
*/
- public void testGetInstalledPrintServices() throws Exception {
- List<PrintServiceInfo> printServices = mIPrintManager.getInstalledPrintServices(mUserId);
- assertTrue(printServices.size() >= 2);
+ public void testAddPrintServicesChangeListener() throws Exception {
+ final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener();
+
+ mIPrintManager.addPrintServicesChangeListener(listener, mUserId);
+
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.addPrintServicesChangeListener(null, mUserId);
+ }
+ }, NullPointerException.class);
// Cannot test bad user Id as these tests are allowed to call across users
}
/**
- * test IPrintManager.getEnabledPrintServices
+ * test IPrintManager.removePrintServicesChangeListener
*/
- public void testGetEnabledPrintServices() throws Exception {
- List<PrintServiceInfo> printServices = mIPrintManager.getEnabledPrintServices(mUserId);
+ public void testRemovePrintServicesChangeListener() throws Exception {
+ final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener();
+
+ mIPrintManager.addPrintServicesChangeListener(listener, mUserId);
+ mIPrintManager.removePrintServicesChangeListener(listener, mUserId);
+
+ // Removing unknown listeners is a no-op
+ mIPrintManager.removePrintServicesChangeListener(listener, mUserId);
+
+ mIPrintManager.addPrintServicesChangeListener(listener, mUserId);
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.removePrintServicesChangeListener(null, mUserId);
+ }
+ }, NullPointerException.class);
+
+ // Cannot test bad user Id as these tests are allowed to call across users
+ }
+
+ /**
+ * test IPrintManager.getPrintServices
+ */
+ public void testGetPrintServices() throws Exception {
+ List<PrintServiceInfo> printServices = mIPrintManager.getPrintServices(
+ PrintManager.ALL_SERVICES, mUserId);
assertTrue(printServices.size() >= 2);
+ printServices = mIPrintManager.getPrintServices(0, mUserId);
+ assertEquals(printServices, null);
+
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.getPrintServices(~PrintManager.ALL_SERVICES, mUserId);
+ }
+ }, IllegalArgumentException.class);
+
+ // Cannot test bad user Id as these tests are allowed to call across users
+ }
+
+ /**
+ * test IPrintManager.setPrintServiceEnabled
+ */
+ public void testSetPrintServiceEnabled() throws Exception {
+ final ComponentName printService = mIPrintManager.getPrintServices(
+ PrintManager.ALL_SERVICES, mUserId).get(0).getComponentName();
+
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.setPrintServiceEnabled(printService, false, mUserId);
+ }
+ }, SecurityException.class);
+
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.setPrintServiceEnabled(printService, true, mUserId);
+ }
+ }, SecurityException.class);
+
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.setPrintServiceEnabled(new ComponentName("bad", "name"), true,
+ mUserId);
+ }
+ }, SecurityException.class);
+
+ assertException(new Invokable() {
+ @Override
+ public void run() throws Exception {
+ mIPrintManager.setPrintServiceEnabled(null, true, mUserId);
+ }
+ }, SecurityException.class);
+
// Cannot test bad user Id as these tests are allowed to call across users
}
* test IPrintManager.startPrinterDiscovery
*/
public void testStartPrinterDiscovery() throws Exception {
+ startPrinting();
+
final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
final List<PrinterId> goodPrinters = new ArrayList<>();
goodPrinters.add(mGoodPrinterId);
* test IPrintManager.validatePrinters
*/
public void testValidatePrinters() throws Exception {
+ startPrinting();
+
final List<PrinterId> goodPrinters = new ArrayList<>();
goodPrinters.add(mGoodPrinterId);
* test IPrintManager.startPrinterStateTracking
*/
public void testStartPrinterStateTracking() throws Exception {
+ startPrinting();
+
mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId);
// Bad printers do no cause exceptions
* test IPrintManager.getCustomPrinterIcon
*/
public void testGetCustomPrinterIcon() throws Exception {
+ startPrinting();
+
mIPrintManager.getCustomPrinterIcon(mGoodPrinterId, mUserId);
// Bad printers do no cause exceptions
* test IPrintManager.stopPrinterStateTracking
*/
public void testStopPrinterStateTracking() throws Exception {
+ startPrinting();
+
mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId);
mIPrintManager.stopPrinterStateTracking(mGoodPrinterId, mUserId);
}
@SmallTest
+ public void testFilterDoesNotMatchAnything() throws Exception {
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("en-US"));
+ availableLocales.add(Locale.forLanguageTag("fr-CA"));
+ availableLocales.add(Locale.forLanguageTag("in"));
+ availableLocales.add(Locale.forLanguageTag("ja"));
+ availableLocales.add(Locale.forLanguageTag("fil"));
+
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hans-TW");
+
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(0, dest.size());
+ }
+
+ @SmallTest
public void testFilterByLanguageEmptySource() throws Exception {
final ArrayList<Locale> availableLocales = new ArrayList<>();
@SmallTest
public void testFilterByLanguage() throws Exception {
- final ArrayList<Locale> availableLocales = new ArrayList<>();
- availableLocales.add(Locale.forLanguageTag("en-US"));
- availableLocales.add(Locale.forLanguageTag("fr-CA"));
- availableLocales.add(Locale.forLanguageTag("in"));
- availableLocales.add(Locale.forLanguageTag("ja"));
- availableLocales.add(Locale.forLanguageTag("fil"));
+ {
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("en-US"));
+ availableLocales.add(Locale.forLanguageTag("fr-CA"));
+ availableLocales.add(Locale.forLanguageTag("in"));
+ availableLocales.add(Locale.forLanguageTag("ja"));
+ availableLocales.add(Locale.forLanguageTag("fil"));
- final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP");
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP");
- final ArrayList<Locale> dest = new ArrayList<>();
- LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
- assertEquals(3, dest.size());
- assertEquals(availableLocales.get(1), dest.get(0)); // "fr-CA"
- assertEquals(availableLocales.get(0), dest.get(1)); // "en-US"
- assertEquals(availableLocales.get(3), dest.get(2)); // "ja"
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(3, dest.size());
+ assertEquals(availableLocales.get(1), dest.get(0)); // "fr-CA"
+ assertEquals(availableLocales.get(0), dest.get(1)); // "en-US"
+ assertEquals(availableLocales.get(3), dest.get(2)); // "ja"
+ }
+ {
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("en-US"));
+ availableLocales.add(Locale.forLanguageTag("en-GB"));
+ availableLocales.add(Locale.forLanguageTag("en-IN"));
+
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("en-US");
+
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(0), dest.get(0)); // "en-US"
+ }
}
@SmallTest
assertEquals(availableLocales.get(1), dest.get(0)); // "en-CA"
}
}
+
+ @SmallTest
+ public void testFilterByLanguageFallbackRules() throws Exception {
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-BA"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-CS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-ME"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(7), dest.get(0)); // "sr-Latn-RS"
+ }
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS-x-android");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-BA"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-CS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-ME"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(7), dest.get(0)); // "sr-Latn-RS"
+ }
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-BA-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-CS-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-ME-x-android"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-RS-x-android"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(7), dest.get(0)); // "sr-Latn-RS-x-android"
+ }
+
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(2), dest.get(0)); // "sr-Latn"
+ }
+
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr"));
+ availableLocales.add(Locale.forLanguageTag("sr-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(0), dest.get(0)); // "sr"
+ }
+
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr"));
+ availableLocales.add(Locale.forLanguageTag("sr-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(2), dest.get(0)); // "sr-Latn"
+ }
+
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr"));
+ availableLocales.add(Locale.forLanguageTag("sr-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(0), dest.get(0)); // "sr"
+ }
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+ availableLocales.add(Locale.forLanguageTag("sr-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(1), dest.get(0)); // "sr-RS"
+ }
+
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(0), dest.get(0)); // "sr-Cyrl-RS"
+ }
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(0), dest.get(0)); // "sr-Latn-RS"
+ }
+ }
+
+ public void testFilterKnownLimitation() throws Exception {
+ // Following test cases are not for intentional behavior but checks for preventing the
+ // behavior from becoming worse.
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("ja-Hrkt");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("ja-Jpan"));
+ availableLocales.add(Locale.forLanguageTag("ja-Hrkt"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ // Should be ja-Jpan since it supports ja-Hrkt and listed before ja-Hrkt.
+ assertEquals(availableLocales.get(1), dest.get(0));
+ }
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hani");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("zh-Hans"));
+ availableLocales.add(Locale.forLanguageTag("zh-Hant"));
+ availableLocales.add(Locale.forLanguageTag("zh-Hanb"));
+ availableLocales.add(Locale.forLanguageTag("zh-Hani"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ // Should be zh-Hans since it supports zh-Hani. Also zh-Hant, zh-Hanb supports zh-Hani.
+ assertEquals(availableLocales.get(3), dest.get(0));
+ }
+ }
}
mutable Mutex mLock;
- // Mutex that controls access to the list of pre-filtered configurations
- // to check when looking up entries.
- // When iterating over a bag, the mLock mutex is locked. While mLock is locked,
- // we do resource lookups.
- // Mutex is not reentrant, so we must use a different lock than mLock.
- mutable Mutex mFilteredConfigLock;
-
status_t mError;
ResTable_config mParams;
#include <string.h>
#include <limits>
-#include <memory>
#include <type_traits>
#include <androidfw/ByteBucketArray.h>
StringPoolRef keyStr;
};
-template <typename T>
-using SharedVector = std::shared_ptr<Vector<T>>;
-
struct ResTable::Type
{
Type(const Header* _header, const Package* _package, size_t count)
const uint32_t* typeSpecFlags;
IdmapEntries idmapEntries;
Vector<const ResTable_type*> configs;
-
- // The set of configurations that match the current parameters.
- // This will be swapped with a new set when the parameters change.
- SharedVector<const ResTable_type*> filteredConfigs;
};
struct ResTable::Package
void ResTable::setParameters(const ResTable_config* params)
{
- AutoMutex _lock(mLock);
- AutoMutex _lock2(mFilteredConfigLock);
-
+ mLock.lock();
if (kDebugTableGetEntry) {
ALOGI("Setting parameters: %s\n", params->toString().string());
}
mParams = *params;
- for (size_t p = 0; p < mPackageGroups.size(); p++) {
- PackageGroup* packageGroup = mPackageGroups.editItemAt(p);
+ for (size_t i=0; i<mPackageGroups.size(); i++) {
if (kDebugTableNoisy) {
- ALOGI("CLEARING BAGS FOR GROUP %zu!", p);
- }
- packageGroup->clearBagCache();
-
- for (size_t t = 0; t < packageGroup->types.size(); t++) {
- TypeList& typeList = packageGroup->types.editItemAt(t);
- for (size_t ts = 0; ts < typeList.size(); ts++) {
- Type* type = typeList.editItemAt(ts);
-
- SharedVector<const ResTable_type*> newFilteredConfigs =
- std::make_shared<Vector<const ResTable_type*>>();
- for (size_t ti = 0; ti < type->configs.size(); ti++) {
- ResTable_config config;
- config.copyFromDtoH(type->configs[ti]->config);
-
- if (config.match(mParams)) {
- newFilteredConfigs->add(type->configs[ti]);
- }
- }
-
- if (kDebugTableNoisy) {
- ALOGD("Updating pkg=%zu type=%zu with %zu filtered configs",
- p, t, newFilteredConfigs->size());
- }
- type->filteredConfigs = newFilteredConfigs;
- }
+ ALOGI("CLEARING BAGS FOR GROUP %zu!", i);
}
+ mPackageGroups[i]->clearBagCache();
}
+ mLock.unlock();
}
void ResTable::getParameters(ResTable_config* params) const
specFlags = -1;
}
- const Vector<const ResTable_type*>* candidateConfigs = &typeSpec->configs;
-
- SharedVector<const ResTable_type*> filteredConfigs;
- if (config && memcmp(&mParams, config, sizeof(mParams)) == 0) {
- // Grab the lock first so we can safely get the current filtered list.
- AutoMutex _lock(mFilteredConfigLock);
-
- // This configuration is equal to the one we have previously cached for,
- // so use the filtered configs.
-
- if (typeSpec->filteredConfigs) {
- // Grab a reference to the shared_ptr so it doesn't get destroyed while
- // going through this list.
- filteredConfigs = typeSpec->filteredConfigs;
-
- // Use this filtered list.
- candidateConfigs = filteredConfigs.get();
- }
- }
-
- const size_t numConfigs = candidateConfigs->size();
+ const size_t numConfigs = typeSpec->configs.size();
for (size_t c = 0; c < numConfigs; c++) {
- const ResTable_type* const thisType = candidateConfigs->itemAt(c);
+ const ResTable_type* const thisType = typeSpec->configs[c];
if (thisType == NULL) {
continue;
}
mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
}
- bool quickRejected = properties.getClipToBounds()
- && mCanvasState.quickRejectConservative(0, 0, width, height);
+ bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty()
+ || (properties.getClipToBounds()
+ && mCanvasState.quickRejectConservative(0, 0, width, height));
if (!quickRejected) {
// not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
if (node.getLayer()) {
*/
public static final int ERROR_SESSION_NOT_OPENED = 5;
+ /**
+ * This indicates that an operation was attempted that could not be
+ * supported by the crypto system of the device in its current
+ * configuration. It may occur when the license policy requires
+ * device security features that aren't supported by the device,
+ * or due to an internal error in the crypto system that prevents
+ * the specified security policy from being met.
+ */
+ public static final int ERROR_UNSUPPORTED_OPERATION = 6;
+
/** @hide */
@IntDef({
ERROR_NO_KEY,
ERROR_RESOURCE_BUSY,
ERROR_INSUFFICIENT_OUTPUT_PROTECTION,
ERROR_SESSION_NOT_OPENED,
+ ERROR_UNSUPPORTED_OPERATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface CryptoErrorCode {}
jint cryptoErrorResourceBusy;
jint cryptoErrorInsufficientOutputProtection;
jint cryptoErrorSessionNotOpened;
+ jint cryptoErrorUnsupportedOperation;
} gCryptoErrorCodes;
static struct CodecActionCodes {
err = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
defaultMsg = "Attempted to use a closed session";
break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ err = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
+ defaultMsg = "Operation not supported in this configuration";
+ break;
default: /* Other negative DRM error codes go out as is. */
break;
}
gCryptoErrorCodes.cryptoErrorSessionNotOpened =
env->GetStaticIntField(clazz.get(), field);
+ field = env->GetStaticFieldID(clazz.get(), "ERROR_UNSUPPORTED_OPERATION", "I");
+ CHECK(field != NULL);
+ gCryptoErrorCodes.cryptoErrorUnsupportedOperation =
+ env->GetStaticIntField(clazz.get(), field);
+
clazz.reset(env->FindClass("android/media/MediaCodec$CodecException"));
CHECK(clazz.get() != NULL);
field = env->GetStaticFieldID(clazz.get(), "ACTION_TRANSIENT", "I");
android:name=".ui.PrintActivity"
android:configChanges="screenSize|smallestScreenSize|orientation"
android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
- android:theme="@style/PrintActivity">
+ android:theme="@style/Theme.PrintActivity">
<intent-filter>
<action android:name="android.print.PRINT_DIALOG" />
<category android:name="android.intent.category.DEFAULT" />
<activity
android:name=".ui.SelectPrinterActivity"
android:label="@string/all_printers_label"
- android:theme="@android:style/Theme.Material.Settings"
+ android:theme="@style/Theme.SelectPrinterActivity"
+ android:exported="false">
+ </activity>
+
+ <activity
+ android:name=".ui.AddPrinterActivity"
+ android:label="@string/print_add_printer"
+ android:theme="@style/Theme.AddPrinterActivity"
android:exported="false">
</activity>
android:viewportHeight="24.0">
<path
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
- android:fillColor="#FFFFFF"/>
+ android:fillColor="?android:attr/colorAccent"/>
</vector>
\ No newline at end of file
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@*android:drawable/ic_print"
- android:tint="@color/promoted_action_background_color" />
+ android:tint="?android:attr/colorAccent" />
android:state_selected="true">
<bitmap
android:src="@drawable/ic_check_circle"
- android:tint="@color/promoted_action_background_color">
+ android:tint="?android:attr/colorAccent">
</bitmap>
</item>
<item>
<bitmap
android:src="@drawable/ic_remove_circle"
- android:tint="@color/promoted_action_background_color">
+ android:tint="?android:attr/colorAccent">
</bitmap>
</item>
android:shape="oval">
<solid
- android:color="@color/promoted_action_background_color">
+ android:color="?android:attr/colorAccent">
</solid>
<size
--- /dev/null
+<?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="match_parent"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip">
+
+ <ListView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-16dip"
+ android:id="@android:id/list" />
+
+</LinearLayout>
--- /dev/null
+<?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:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <TextView android:id="@+id/text"
+ style="?android:attr/listSeparatorTextViewStyle" />
+
+</LinearLayout>
--- /dev/null
+<?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:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <ImageView
+ android:layout_width="24dip"
+ android:layout_height="24dip"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@null"
+ android:layout_marginEnd="16dip"
+ android:src="@drawable/ic_add" />
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:text="@string/print_add_printer" />
+
+ </RelativeLayout>
+
+</LinearLayout>
--- /dev/null
+<?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:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="56dip">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:text="@string/all_services_title" />
+
+ </RelativeLayout>
+
+</LinearLayout>
--- /dev/null
+<?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:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="40dip"
+ android:layout_height="40dip"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@null" />
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:singleLine="true"
+ android:ellipsize="end" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:text="@string/enable_print_service" />
+
+ </RelativeLayout>
+
+</LinearLayout>
--- /dev/null
+<?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:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="40dip"
+ android:layout_height="40dip"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@null" />
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:singleLine="true"
+ android:ellipsize="end" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary" />
+
+ </RelativeLayout>
+
+</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:paddingStart="8dip"
- android:paddingEnd="8dip"
- android:minHeight="56dip"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ style="?android:attr/spinnerItemStyle"
android:orientation="horizontal"
android:gravity="start|center_vertical">
android:visibility="invisible">
</ImageView>
- <LinearLayout
+ <RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
android:layout_marginStart="8dip"
android:duplicateParentState="true">
android:textAppearance="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="end"
- android:textIsSelectable="false"
- android:gravity="top|start"
android:textColor="?android:attr/textColorPrimary"
android:duplicateParentState="true">
</TextView>
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_below="@id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:ellipsize="end"
- android:textIsSelectable="false"
android:visibility="gone"
android:textColor="?android:attr/textColorSecondary"
android:duplicateParentState="true">
</TextView>
- </LinearLayout>
+ </RelativeLayout>
</LinearLayout>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textIsSelectable="false"
android:textColor="?android:attr/textColorPrimary"
android:paddingStart="20dip"
- android:paddingEnd="8dip"
- android:minHeight="56dip"
- android:orientation="horizontal"
+ style="?android:attr/spinnerItemStyle"
android:text="@string/destination_default_text"
android:gravity="start|center_vertical" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
+ android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:gravity="start|center_vertical">
android:layout_width="40dip"
android:layout_height="40dip"
android:layout_gravity="center_vertical"
- android:layout_marginTop="8dip"
- android:layout_marginBottom="8dip"
android:duplicateParentState="true"
android:contentDescription="@null"
android:visibility="invisible">
</ImageView>
<RelativeLayout
- android:layout_width="0dip"
+ android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_marginStart="16dip"
android:duplicateParentState="true">
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?android:attr/textAppearanceListItem"
android:singleLine="true"
android:ellipsize="end"
- android:textIsSelectable="false"
- android:layout_alignParentTop="true"
- android:layout_alignParentStart="true"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
- android:textColor="?android:attr/textColorPrimary"
android:duplicateParentState="true">
</TextView>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
- android:layout_alignParentStart="true"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:singleLine="true"
android:ellipsize="end"
- android:textIsSelectable="false"
android:visibility="gone"
- android:textColor="?android:attr/textColorSecondary"
- android:textAlignment="viewStart"
android:duplicateParentState="true">
</TextView>
</RelativeLayout>
- <ImageView
+ <!-- wrapper for image view to increase the touch target size -->
+ <LinearLayout
android:id="@+id/more_info"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingLeft="16dip"
- android:contentDescription="@string/printer_info_desc"
- android:src="@drawable/ic_info"
- android:tint="?android:attr/colorControlNormal"
- android:tintMode="src_in"
+ android:layout_height="fill_parent"
android:visibility="gone">
- </ImageView>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="16dip"
+ android:contentDescription="@string/printer_info_desc"
+ android:src="@drawable/ic_info"
+ android:tint="?android:attr/colorControlNormal"
+ android:tintMode="src_in" />
+ </LinearLayout>
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scrollbarStyle="outsideOverlay"
- android:cacheColorHint="@android:color/transparent"
- android:scrollbarAlwaysDrawVerticalTrack="true" >
- </ListView>
+ android:layout_height="fill_parent" />
<FrameLayout
android:id="@+id/empty_print_state"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
- style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal">
+ style="?android:attr/progressBarStyleHorizontal">
</ProgressBar>
+ <Button
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/print_add_printer"
+ android:textAllCaps="true" />
+
</LinearLayout>
</FrameLayout>
android:imeOptions="actionSearch">
</item>
- <item
- android:id="@+id/action_add_printer"
- android:title="@string/print_add_printer"
- android:icon="@drawable/ic_add"
- android:showAsAction="ifRoom"
- android:alphabeticShortcut="a">
- </item>
-
</menu>
<color name="print_preview_background_color">#F2F1F2</color>
- <color name="promoted_action_background_color">#FF80CBC4</color>
-
<color name="material_grey_500">#ffa3a3a3</color>
</resources>
<string name="mediasize_default">ISO_A4</string>
<string name="mediasize_standard">@string/mediasize_standard_iso</string>
+ <string name="uri_package_details">market://details?id=%1$s</string>
+
</resources>
<!-- Utterance to announce that the search box is hidden. This is spoken to a blind user. [CHAR LIMIT=none] -->
<string name="print_search_box_hidden_utterance">Search box hidden</string>
- <!-- Title of the action bar button to got to add a printer. [CHAR LIMIT=25] -->
+ <!-- Label of add printers button when no printers are found. [CHAR LIMIT=25] -->
<string name="print_add_printer">Add printer</string>
<!-- Title of the menu item to select a printer. [CHAR LIMIT=25] -->
<string name="printer_info_desc">More information about this printer</string>
<!-- Notification that print services as disabled. [CHAR LIMIT=50] -->
- <string name="print_services_disabled_toast">Some print services are disabled.</string>
-
- <!-- Add printer dialog -->
-
- <!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
- <string name="choose_print_service">Choose print service</string>
+ <string name="print_services_disabled_toast">Some print services are disabled</string>
<!-- Title for the prompt shown as a placeholder if no printers are found while not searching. [CHAR LIMIT=50] -->
<string name="print_searching_for_printers">Searching for printers</string>
<!-- Title for the prompt shown as a placeholder if there are no printers while searching. [CHAR LIMIT=50] -->
<string name="print_no_printers">No printers found</string>
+ <!-- Add printer activity -->
+
+ <!-- Subtitle for services that cannot add printers. [CHAR LIMIT=50] -->
+ <string name="cannot_add_printer">Cannot add printers</string>
+
+ <!-- Subtitle for services that can add printers. [CHAR LIMIT=50] -->
+ <string name="select_to_add_printers">Select to add printer</string>
+
+ <!-- Subtitle for disabled services. [CHAR LIMIT=50] -->
+ <string name="enable_print_service">Select to enable</string>
+
+ <!-- Header for the list of enabled print services. [CHAR LIMIT=50] -->
+ <string name="enabled_services_title">Enabled services</string>
+
+ <!-- Header for the list of recommended print services. [CHAR LIMIT=50] -->
+ <string name="recommended_services_title">Recommended services</string>
+
+ <!-- Header for the list of disabled print services. [CHAR LIMIT=50] -->
+ <string name="disabled_services_title">Disabled services</string>
+
+ <!-- Label for the list item that links to the list of all print services. [CHAR LIMIT=50] -->
+ <string name="all_services_title">All services</string>
+
<!-- Notifications -->
<!-- Template for the notification label for a printing print job. [CHAR LIMIT=25] -->
--- /dev/null
+<?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.
+-->
+
+<resources>
+ <!-- Preference styles -->
+ <eat-comment/>
+
+ <style name="ListItemSecondary" parent="@android:style/TextAppearance.Material.Body1">
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="ListSeparator">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dip</item>
+ <item name="android:layout_marginBottom">16dip</item>
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+</resources>
-->
<resources>
+ <style name="Theme.AddPrinterActivity" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <item name="android:listSeparatorTextViewStyle">@style/ListSeparator</item>
+ <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
+ </style>
+
+ <style name="Theme.SelectPrinterActivity"
+ parent="android:style/Theme.DeviceDefault.Light.DarkActionBar">
+ <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
+ </style>
- <style name="PrintActivity" parent="@android:style/Theme.DeviceDefault">
+ <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
--- /dev/null
+/*
+ * 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.printspooler.ui;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ListActivity;
+import android.app.LoaderManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.print.PrintManager;
+import android.print.PrintServicesLoader;
+import android.printservice.PrintServiceInfo;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.printspooler.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is an activity for adding a printer or. It consists of a list fed from three adapters:
+ * <ul>
+ * <li>{@link #mEnabledServicesAdapter} for all enabled services. If a service has an {@link
+ * PrintServiceInfo#getAddPrintersActivityName() add printer activity} this is started
+ * when the item is clicked.</li>
+ * <li>{@link #mDisabledServicesAdapter} for all disabled services. Once clicked the settings page
+ * for this service is opened.</li>
+ * <li>{@link RecommendedServicesAdapter} for a link to all services. If this item is clicked
+ * the market app is opened to show all print services.</li>
+ * </ul>
+ */
+public class AddPrinterActivity extends ListActivity implements
+ LoaderManager.LoaderCallbacks<List<PrintServiceInfo>>,
+ AdapterView.OnItemClickListener {
+ private static final String LOG_TAG = "AddPrinterActivity";
+
+ /** Ids for the loaders */
+ private static final int LOADER_ID_ENABLED_SERVICES = 1;
+ private static final int LOADER_ID_DISABLED_SERVICES = 2;
+
+ /**
+ * The enabled services list. This is filled from the {@link #LOADER_ID_ENABLED_SERVICES}
+ * loader in {@link #onLoadFinished}.
+ */
+ private EnabledServicesAdapter mEnabledServicesAdapter;
+
+ /**
+ * The disabled services list. This is filled from the {@link #LOADER_ID_DISABLED_SERVICES}
+ * loader in {@link #onLoadFinished}.
+ */
+ private DisabledServicesAdapter mDisabledServicesAdapter;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.add_printer_activity);
+
+ mEnabledServicesAdapter = new EnabledServicesAdapter();
+ mDisabledServicesAdapter = new DisabledServicesAdapter();
+
+ ArrayList<ActionAdapter> adapterList = new ArrayList<>(3);
+ adapterList.add(mEnabledServicesAdapter);
+ adapterList.add(new RecommendedServicesAdapter());
+ adapterList.add(mDisabledServicesAdapter);
+
+ setListAdapter(new CombinedAdapter(adapterList));
+
+ getListView().setOnItemClickListener(this);
+
+ getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, this);
+ getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, this);
+ // TODO: Load recommended services
+ }
+
+ @Override
+ public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_ID_ENABLED_SERVICES:
+ return new PrintServicesLoader(
+ (PrintManager) getSystemService(Context.PRINT_SERVICE), this,
+ PrintManager.ENABLED_SERVICES);
+ case LOADER_ID_DISABLED_SERVICES:
+ return new PrintServicesLoader(
+ (PrintManager) getSystemService(Context.PRINT_SERVICE), this,
+ PrintManager.DISABLED_SERVICES);
+ // TODO: Load recommended services
+ default:
+ // not reached
+ return null;
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ ((ActionAdapter) getListAdapter()).performAction(position);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
+ List<PrintServiceInfo> data) {
+ switch (loader.getId()) {
+ case LOADER_ID_ENABLED_SERVICES:
+ mEnabledServicesAdapter.updateData(data);
+ break;
+ case LOADER_ID_DISABLED_SERVICES:
+ mDisabledServicesAdapter.updateData(data);
+ break;
+ // TODO: Load recommended services
+ default:
+ // not reached
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
+ if (!isFinishing()) {
+ switch (loader.getId()) {
+ case LOADER_ID_ENABLED_SERVICES:
+ mEnabledServicesAdapter.updateData(null);
+ break;
+ case LOADER_ID_DISABLED_SERVICES:
+ mDisabledServicesAdapter.updateData(null);
+ break;
+ // TODO: Reset recommended services
+ default:
+ // not reached
+ }
+ }
+ }
+
+ /**
+ * Marks an adapter that can can perform an action for a position in it's list.
+ */
+ private abstract class ActionAdapter extends BaseAdapter {
+ /**
+ * Perform the action for a position in the list.
+ *
+ * @param position The position of the item
+ */
+ abstract void performAction(@IntRange(from = 0) int position);
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+ }
+
+ /**
+ * An adapter presenting multiple sub adapters as a single combined adapter.
+ */
+ private class CombinedAdapter extends ActionAdapter {
+ /** The adapters to combine */
+ private final @NonNull ArrayList<ActionAdapter> mAdapters;
+
+ /**
+ * Create a combined adapter.
+ *
+ * @param adapters the list of adapters to combine
+ */
+ CombinedAdapter(@NonNull ArrayList<ActionAdapter> adapters) {
+ mAdapters = adapters;
+
+ final int numAdapters = mAdapters.size();
+ for (int i = 0; i < numAdapters; i++) {
+ mAdapters.get(i).registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ notifyDataSetChanged();
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getCount() {
+ int totalCount = 0;
+
+ final int numAdapters = mAdapters.size();
+ for (int i = 0; i < numAdapters; i++) {
+ totalCount += mAdapters.get(i).getCount();
+ }
+
+ return totalCount;
+ }
+
+ /**
+ * Find the sub adapter and the position in the sub-adapter the position in the combined
+ * adapter refers to.
+ *
+ * @param position The position in the combined adapter
+ *
+ * @return The pair of adapter and position in sub adapter
+ */
+ private @NonNull Pair<ActionAdapter, Integer> getSubAdapter(int position) {
+ final int numAdapters = mAdapters.size();
+ for (int i = 0; i < numAdapters; i++) {
+ ActionAdapter adapter = mAdapters.get(i);
+
+ if (position < adapter.getCount()) {
+ return new Pair<>(adapter, position);
+ } else {
+ position -= adapter.getCount();
+ }
+ }
+
+ throw new IllegalArgumentException("Invalid position");
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ int numLowerViewTypes = 0;
+
+ final int numAdapters = mAdapters.size();
+ for (int i = 0; i < numAdapters; i++) {
+ Adapter adapter = mAdapters.get(i);
+
+ if (position < adapter.getCount()) {
+ return numLowerViewTypes + adapter.getItemViewType(position);
+ } else {
+ numLowerViewTypes += adapter.getViewTypeCount();
+ position -= adapter.getCount();
+ }
+ }
+
+ throw new IllegalArgumentException("Invalid position");
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ int totalViewCount = 0;
+
+ final int numAdapters = mAdapters.size();
+ for (int i = 0; i < numAdapters; i++) {
+ totalViewCount += mAdapters.get(i).getViewTypeCount();
+ }
+
+ return totalViewCount;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
+
+ return realPosition.first.getView(realPosition.second, convertView, parent);
+ }
+
+ @Override
+ public Object getItem(int position) {
+ Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
+
+ return realPosition.first.getItem(realPosition.second);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
+
+ return realPosition.first.isEnabled(realPosition.second);
+ }
+
+ @Override
+ public void performAction(@IntRange(from = 0) int position) {
+ Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position);
+
+ realPosition.first.performAction(realPosition.second);
+ }
+ }
+
+ /**
+ * Superclass for all adapters that just display a list of {@link PrintServiceInfo}.
+ */
+ private abstract class PrintServiceInfoAdapter extends ActionAdapter {
+ /**
+ * Raw data of the list.
+ *
+ * @see #updateData(List)
+ */
+ private @NonNull List<PrintServiceInfo> mServices;
+
+ /**
+ * Create a new adapter.
+ */
+ PrintServiceInfoAdapter() {
+ mServices = Collections.emptyList();
+ }
+
+ /**
+ * Update the data.
+ *
+ * @param services The new raw data.
+ */
+ void updateData(@Nullable List<PrintServiceInfo> services) {
+ if (services == null || services.isEmpty()) {
+ mServices = Collections.emptyList();
+ } else {
+ mServices = services;
+ }
+
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position == 0) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (mServices.isEmpty()) {
+ return 0;
+ } else {
+ return mServices.size() + 1;
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (position == 0) {
+ return null;
+ } else {
+ return mServices.get(position - 1);
+ }
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return position != 0;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+ }
+
+ /**
+ * Adapter for the enabled services.
+ */
+ private class EnabledServicesAdapter extends PrintServiceInfoAdapter {
+ @Override
+ public void performAction(@IntRange(from = 0) int position) {
+ PrintServiceInfo service = (PrintServiceInfo) getItem(position);
+ String addPrinterActivityName = service.getAddPrintersActivityName();
+
+ if (!TextUtils.isEmpty(addPrinterActivityName)) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(new ComponentName(service.getComponentName().getPackageName(),
+ addPrinterActivityName));
+
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot start add printers activity", e);
+ }
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (position == 0) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
+ parent, false);
+ }
+
+ ((TextView) convertView.findViewById(R.id.text))
+ .setText(R.string.enabled_services_title);
+
+ return convertView;
+ }
+
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.enabled_print_services_list_item,
+ parent, false);
+ }
+
+ PrintServiceInfo service = (PrintServiceInfo) getItem(position);
+
+ TextView title = (TextView) convertView.findViewById(R.id.title);
+ ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+ TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle);
+
+ title.setText(service.getResolveInfo().loadLabel(getPackageManager()));
+ icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager()));
+
+ if (TextUtils.isEmpty(service.getAddPrintersActivityName())) {
+ subtitle.setText(getString(R.string.cannot_add_printer));
+ } else {
+ subtitle.setText(getString(R.string.select_to_add_printers));
+ }
+
+ return convertView;
+ }
+ }
+
+ /**
+ * Adapter for the disabled services.
+ */
+ private class DisabledServicesAdapter extends PrintServiceInfoAdapter {
+ @Override
+ public void performAction(@IntRange(from = 0) int position) {
+ ((PrintManager) getSystemService(Context.PRINT_SERVICE)).setPrintServiceEnabled(
+ ((PrintServiceInfo) getItem(position)).getComponentName(), true);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (position == 0) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
+ parent, false);
+ }
+
+ ((TextView) convertView.findViewById(R.id.text))
+ .setText(R.string.disabled_services_title);
+
+ return convertView;
+ }
+
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(
+ R.layout.disabled_print_services_list_item, parent, false);
+ }
+
+ PrintServiceInfo service = (PrintServiceInfo) getItem(position);
+
+ TextView title = (TextView) convertView.findViewById(R.id.title);
+ ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+
+ title.setText(service.getResolveInfo().loadLabel(getPackageManager()));
+ icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager()));
+
+ return convertView;
+ }
+ }
+
+ /**
+ * Adapter for the recommended services.
+ */
+ private class RecommendedServicesAdapter extends ActionAdapter {
+ @Override
+ public int getCount() {
+ return 2;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position == 0) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (position == 0) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header,
+ parent, false);
+ }
+
+ ((TextView) convertView.findViewById(R.id.text))
+ .setText(R.string.recommended_services_title);
+
+ return convertView;
+ }
+
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.all_print_services_list_item,
+ parent, false);
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return position != 0;
+ }
+
+ @Override
+ public void performAction(@IntRange(from = 0) int position) {
+ String searchUri = Settings.Secure
+ .getString(getContentResolver(), Settings.Secure.PRINT_SERVICE_SEARCH_URI);
+
+ if (searchUri != null) {
+ try {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
+ } catch (ActivityNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot start market", e);
+ }
+ }
+ }
+ }
+}
package com.android.printspooler.ui;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Loader;
import android.location.LocationRequest;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.print.PrintManager;
+import android.print.PrintServicesLoader;
import android.print.PrinterDiscoverySession;
import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
import android.print.PrinterId;
}
}
- public FusedPrintersProvider(Context context) {
- super(context);
+ public FusedPrintersProvider(Activity activity, int internalLoaderId) {
+ super(activity);
mLocationLock = new Object();
- mPersistenceManager = new PersistenceManager(context);
- mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ mPersistenceManager = new PersistenceManager(activity, internalLoaderId);
+ mLocationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
}
public void addHistoricalPrinter(PrinterInfo printer) {
mPrinters.clear();
if (mDiscoverySession != null) {
mDiscoverySession.destroy();
- mDiscoverySession = null;
}
}
updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters, getCurrentLocation());
}
- private final class PersistenceManager {
+ private final class PersistenceManager implements
+ LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
private static final String PERSIST_FILE_NAME = "printer_history.xml";
private static final String TAG_PRINTERS = "printers";
private final AtomicFile mStatePersistFile;
+ /**
+ * Whether the enabled print services have been updated since last time the history was
+ * read.
+ */
+ private boolean mAreEnabledServicesUpdated;
+
+ /** The enabled services read when they were last updated */
+ private @NonNull List<PrintServiceInfo> mEnabledServices;
+
private List<Pair<PrinterInfo, Location>> mHistoricalPrinters = new ArrayList<>();
private boolean mReadHistoryCompleted;
private volatile long mLastReadHistoryTimestamp;
- private PersistenceManager(Context context) {
- mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
+ private PersistenceManager(final Activity activity, final int internalLoaderId) {
+ mStatePersistFile = new AtomicFile(new File(activity.getFilesDir(),
PERSIST_FILE_NAME));
+
+ // Initialize enabled services to make sure they are set are the read task might be done
+ // before the loader updated the services the first time.
+ mEnabledServices = ((PrintManager) activity
+ .getSystemService(Context.PRINT_SERVICE))
+ .getPrintServices(PrintManager.ENABLED_SERVICES);
+
+ mAreEnabledServicesUpdated = true;
+
+ // Cannot start a loader while starting another, hence delay this loader
+ (new Handler(activity.getMainLooper())).post(new Runnable() {
+ @Override
+ public void run() {
+ activity.getLoaderManager().initLoader(internalLoaderId, null,
+ PersistenceManager.this);
+ }
+ });
+ }
+
+
+ @Override
+ public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
+ return new PrintServicesLoader(
+ (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE),
+ getContext(), PrintManager.ENABLED_SERVICES);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
+ List<PrintServiceInfo> services) {
+ mAreEnabledServicesUpdated = true;
+ mEnabledServices = services;
+
+ // Ask the fused printer provider to reload which will cause the persistence manager to
+ // reload the history and reconsider the enabled services.
+ if (isStarted()) {
+ forceLoad();
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
+ // no data is cached
}
public boolean isReadHistoryInProgress() {
}
public boolean isHistoryChanged() {
- return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
+ return mAreEnabledServicesUpdated ||
+ mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
}
/**
}
// Ignore printer records whose target services are not enabled.
- PrintManager printManager = (PrintManager) getContext()
- .getSystemService(Context.PRINT_SERVICE);
- List<PrintServiceInfo> services = printManager
- .getEnabledPrintServices();
-
Set<ComponentName> enabledComponents = new ArraySet<>();
- final int installedServiceCount = services.size();
+ final int installedServiceCount = mEnabledServices.size();
for (int i = 0; i < installedServiceCount; i++) {
- ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
+ ServiceInfo serviceInfo = mEnabledServices.get(i).getResolveInfo().serviceInfo;
ComponentName componentName = new ComponentName(
serviceInfo.packageName, serviceInfo.name);
enabledComponents.add(componentName);
}
+ mAreEnabledServicesUpdated = false;
final int printerCount = printers.size();
for (int i = printerCount - 1; i >= 0; i--) {
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
+import android.app.LoaderManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.Loader;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
import android.print.PrintManager;
+import android.print.PrintServicesLoader;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintService;
+import android.printservice.PrintServiceInfo;
import android.provider.DocumentsContract;
import android.text.Editable;
import android.text.TextUtils;
import com.android.printspooler.util.MediaSizeUtils;
import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
import com.android.printspooler.util.PageRangeUtils;
-import com.android.printspooler.util.PrintOptionUtils;
import com.android.printspooler.widget.PrintContentView;
import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
import com.android.printspooler.widget.PrintContentView.OptionsStateController;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
- OptionsStateChangeListener, OptionsStateController {
+ OptionsStateChangeListener, OptionsStateController,
+ LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
private static final String LOG_TAG = "PrintActivity";
private static final boolean DEBUG = false;
private static final String HAS_PRINTED_PREF = "has_printed";
+ private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 1;
+ private static final int LOADER_ID_PRINT_REGISTRY = 2;
+ private static final int LOADER_ID_PRINT_REGISTRY_INT = 3;
+
private static final int ORIENTATION_PORTRAIT = 0;
private static final int ORIENTATION_LANDSCAPE = 1;
private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
- private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
+ private static final int DEST_ADAPTER_ITEM_ID_MORE = Integer.MAX_VALUE - 1;
private static final int STATE_INITIALIZING = 0;
private static final int STATE_CONFIGURING = 1;
/** Observer for changes to the printers */
private PrintersObserver mPrintersObserver;
+ /** Advances options activity name for current printer */
+ private ComponentName mAdvancedPrintOptionsActivity;
+
+ /** Whether at least one print services is enabled or not */
+ private boolean mArePrintServicesEnabled;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
});
+
+ getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this);
}
private void onConnectedToPrintSpooler(final IBinder documentAdapter) {
public void run() {
onPrinterRegistryReady(documentAdapter);
}
- });
+ }, LOADER_ID_PRINT_REGISTRY, LOADER_ID_PRINT_REGISTRY_INT);
}
private void onPrinterRegistryReady(IBinder documentAdapter) {
}
private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
- ComponentName serviceName = printer.getId().getServiceName();
-
- String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName);
- if (TextUtils.isEmpty(activityName)) {
+ if (mAdvancedPrintOptionsActivity == null) {
return;
}
Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName));
+ intent.setComponent(mAdvancedPrintOptionsActivity);
List<ResolveInfo> resolvedActivities = getPackageManager()
.queryIntentActivities(intent, 0);
}
}
+ @Override
+ public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
+ return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this,
+ PrintManager.ENABLED_SERVICES);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
+ List<PrintServiceInfo> services) {
+ ComponentName newAdvancedPrintOptionsActivity = null;
+ if (mCurrentPrinter != null && services != null) {
+ final int numServices = services.size();
+ for (int i = 0; i < numServices; i++) {
+ PrintServiceInfo service = services.get(i);
+
+ if (service.getComponentName().equals(mCurrentPrinter.getId().getServiceName())) {
+ String advancedOptionsActivityName = service.getAdvancedOptionsActivityName();
+
+ if (!TextUtils.isEmpty(advancedOptionsActivityName)) {
+ newAdvancedPrintOptionsActivity = new ComponentName(
+ service.getComponentName().getPackageName(),
+ advancedOptionsActivityName);
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (!Objects.equals(newAdvancedPrintOptionsActivity, mAdvancedPrintOptionsActivity)) {
+ mAdvancedPrintOptionsActivity = newAdvancedPrintOptionsActivity;
+ updateOptionsUi();
+ }
+
+ boolean newArePrintServicesEnabled = services != null && !services.isEmpty();
+ if (mArePrintServicesEnabled != newArePrintServicesEnabled) {
+ mArePrintServicesEnabled = newArePrintServicesEnabled;
+
+ // Reload mDestinationSpinnerAdapter as mArePrintServicesEnabled changed and the adapter
+ // reads that in DestinationAdapter#getMoreItemTitle
+ if (mDestinationSpinnerAdapter != null) {
+ mDestinationSpinnerAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
+ if (!isFinishing()) {
+ onLoadFinished(loader, null);
+ }
+ }
+
/**
* A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically
* dismissed if the same {@link PrintService} gets approved by another
}
// Advanced print options
- ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
- if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
- this, serviceName))) {
+ if (mAdvancedPrintOptionsActivity != null) {
mMoreOptionsButton.setVisibility(View.VISIBLE);
mMoreOptionsButton.setEnabled(true);
} else {
if (position == 0) {
return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
} else if (position == 1) {
- return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+ return DEST_ADAPTER_ITEM_ID_MORE;
}
} else {
if (position == 1) {
return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
}
if (position == getCount() - 1) {
- return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+ return DEST_ADAPTER_ITEM_ID_MORE;
}
}
return position;
return view;
}
+ private String getMoreItemTitle() {
+ if (mArePrintServicesEnabled) {
+ return getString(R.string.all_printers);
+ } else {
+ return getString(R.string.print_add_printer);
+ }
+ }
+
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mShowDestinationPrompt) {
title = printerHolder.printer.getName();
icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
} else if (position == 1) {
- title = getString(R.string.all_printers);
+ title = getMoreItemTitle();
}
} else {
if (position == 1 && getPdfPrinter() != null) {
title = printerHolder.printer.getName();
icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
} else if (position == getCount() - 1) {
- title = getString(R.string.all_printers);
+ title = getMoreItemTitle();
} else {
PrinterHolder printerHolder = (PrinterHolder) getItem(position);
PrinterInfo printInfo = printerHolder.printer;
}
iconView.setImageDrawable(icon);
} else {
- iconView.setVisibility(View.GONE);
+ iconView.setVisibility(View.INVISIBLE);
}
return convertView;
PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
if (updatedPrinter != null) {
printerHolder.printer = updatedPrinter;
+ printerHolder.removed = false;
} else {
printerHolder.removed = true;
}
updateDocument(false);
}
+ // Force a reload of the enabled print services to update mAdvancedPrintOptionsActivity
+ // in onLoadFinished();
+ getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad();
+
updateOptionsUi();
updateSummary();
}
return;
}
- if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
+ if (id == DEST_ADAPTER_ITEM_ID_MORE) {
startSelectPrinterActivity();
return;
}
public class PrinterRegistry {
- private static final int LOADER_ID_PRINTERS_LOADER = 1;
+ private final int mLoaderId;
private final Activity mActivity;
public void onPrintersInvalid();
}
- public PrinterRegistry(Activity activity, Runnable readyCallback) {
+ public PrinterRegistry(Activity activity, Runnable readyCallback, int loaderId,
+ int internalLoaderId) {
+ mLoaderId = loaderId;
mActivity = activity;
mReadyCallback = readyCallback;
mHandler = new MyHandler(activity.getMainLooper());
- activity.getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER,
- null, mLoaderCallbacks);
+
+ Bundle loaderData = new Bundle(1);
+ loaderData.putInt(null, internalLoaderId);
+
+ activity.getLoaderManager().initLoader(loaderId, loaderData, mLoaderCallbacks);
}
public void setOnPrintersChangeListener(OnPrintersChangeListener listener) {
}
private FusedPrintersProvider getPrinterProvider() {
- Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
+ Loader<?> loader = mActivity.getLoaderManager().getLoader(mLoaderId);
return (FusedPrintersProvider) loader;
}
new LoaderCallbacks<List<PrinterInfo>>() {
@Override
public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
- if (loader.getId() == LOADER_ID_PRINTERS_LOADER) {
- mPrinters.clear();
- if (mOnPrintersChangeListener != null) {
- // Post a message as we are in onLoadFinished and certain operations
- // are not allowed in this callback, such as fragment transactions.
- // Clients should not handle this explicitly.
- mHandler.obtainMessage(MyHandler.MSG_PRINTERS_INVALID,
- mOnPrintersChangeListener).sendToTarget();
- }
+ mPrinters.clear();
+ if (mOnPrintersChangeListener != null) {
+ // Post a message as we are in onLoadFinished and certain operations
+ // are not allowed in this callback, such as fragment transactions.
+ // Clients should not handle this explicitly.
+ mHandler.obtainMessage(MyHandler.MSG_PRINTERS_INVALID,
+ mOnPrintersChangeListener).sendToTarget();
}
}
// LoaderCallbacks#onLoadFinished
@Override
public void onLoadFinished(Loader<List<PrinterInfo>> loader, List<PrinterInfo> printers) {
- if (loader.getId() == LOADER_ID_PRINTERS_LOADER) {
- mPrinters.clear();
- mPrinters.addAll(printers);
- if (mOnPrintersChangeListener != null) {
- // Post a message as we are in onLoadFinished and certain operations
- // are not allowed in this callback, such as fragment transactions.
- // Clients should not handle this explicitly.
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = mOnPrintersChangeListener;
- args.arg2 = printers;
- mHandler.obtainMessage(MyHandler.MSG_PRINTERS_CHANGED, args).sendToTarget();
- }
- if (!mReady) {
- mReady = true;
- if (mReadyCallback != null) {
- mReadyCallback.run();
- }
+ mPrinters.clear();
+ mPrinters.addAll(printers);
+ if (mOnPrintersChangeListener != null) {
+ // Post a message as we are in onLoadFinished and certain operations
+ // are not allowed in this callback, such as fragment transactions.
+ // Clients should not handle this explicitly.
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mOnPrintersChangeListener;
+ args.arg2 = printers;
+ mHandler.obtainMessage(MyHandler.MSG_PRINTERS_CHANGED, args).sendToTarget();
+ }
+ if (!mReady) {
+ mReady = true;
+ if (mReadyCallback != null) {
+ mReadyCallback.run();
}
}
}
// LoaderCallbacks#onCreateLoader
@Override
public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
- if (id == LOADER_ID_PRINTERS_LOADER) {
- return new FusedPrintersProvider(mActivity);
- }
- return null;
+ return new FusedPrintersProvider(mActivity, args.getInt(null));
}
};
package com.android.printspooler.ui;
import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
+import android.app.LoaderManager;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
-import android.content.pm.ActivityInfo;
+import android.content.Loader;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.database.ContentObserver;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.print.PrintManager;
+import android.print.PrintServicesLoader;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintServiceInfo;
import android.view.accessibility.AccessibilityManager;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.content.PackageMonitor;
import com.android.printspooler.R;
import java.util.ArrayList;
/**
* This is an activity for selecting a printer.
*/
-public final class SelectPrinterActivity extends Activity {
+public final class SelectPrinterActivity extends Activity implements
+ LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
private static final String LOG_TAG = "SelectPrinterFragment";
- public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
-
- private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG =
- "FRAGMENT_TAG_ADD_PRINTER_DIALOG";
+ private static final int LOADER_ID_PRINT_REGISTRY = 1;
+ private static final int LOADER_ID_PRINT_REGISTRY_INT = 2;
+ private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 3;
- private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS =
- "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS";
+ public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
+ private static final String KEY_NOT_FIRST_CREATE = "KEY_NOT_FIRST_CREATE";
+
/** If there are any enabled print services */
private boolean mHasEnabledPrintServices;
- private final ArrayList<PrintServiceInfo> mAddPrinterServices =
- new ArrayList<>();
-
private PrinterRegistry mPrinterRegistry;
private ListView mListView;
private AnnounceFilterResult mAnnounceFilterResult;
- /** Monitor if new print services get enabled or disabled */
- private ContentObserver mPrintServicesDisabledObserver;
- private PackageMonitor mPackageObserver;
+ private void startAddPrinterActivity() {
+ startActivity(new Intent(this, AddPrinterActivity.class));
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.select_printer_activity);
- mPrinterRegistry = new PrinterRegistry(this, null);
+ mPrinterRegistry = new PrinterRegistry(this, null, LOADER_ID_PRINT_REGISTRY,
+ LOADER_ID_PRINT_REGISTRY_INT);
// Hook up the list view.
mListView = (ListView) findViewById(android.R.id.list);
}
PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
- onPrinterSelected(printer.getId());
+
+ if (printer == null) {
+ startAddPrinterActivity();
+ } else {
+ onPrinterSelected(printer.getId());
+ }
+ }
+ });
+
+ findViewById(R.id.button).setOnClickListener(new OnClickListener() {
+ @Override public void onClick(View v) {
+ startAddPrinterActivity();
}
});
registerForContextMenu(mListView);
- // Display a notification about disabled services if there are disabled services
- String disabledServicesSetting = Settings.Secure.getString(getContentResolver(),
- Settings.Secure.DISABLED_PRINT_SERVICES);
- if (!TextUtils.isEmpty(disabledServicesSetting)) {
- Toast.makeText(this, getString(R.string.print_services_disabled_toast),
- Toast.LENGTH_LONG).show();
+ getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this);
+
+ // On first creation:
+ //
+ // If no services are installed, instantly open add printer dialog.
+ // If some are disabled and some are enabled show a toast to notify the user
+ if (savedInstanceState == null || !savedInstanceState.getBoolean(KEY_NOT_FIRST_CREATE)) {
+ List<PrintServiceInfo> allServices =
+ ((PrintManager) getSystemService(Context.PRINT_SERVICE))
+ .getPrintServices(PrintManager.ALL_SERVICES);
+ boolean hasEnabledServices = false;
+ boolean hasDisabledServices = false;
+
+ if (allServices != null) {
+ final int numServices = allServices.size();
+ for (int i = 0; i < numServices; i++) {
+ if (allServices.get(i).isEnabled()) {
+ hasEnabledServices = true;
+ } else {
+ hasDisabledServices = true;
+ }
+ }
+ }
+
+ if (!hasEnabledServices) {
+ startAddPrinterActivity();
+ } else if (hasDisabledServices) {
+ String disabledServicesSetting = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.DISABLED_PRINT_SERVICES);
+ if (!TextUtils.isEmpty(disabledServicesSetting)) {
+ Toast.makeText(this, getString(R.string.print_services_disabled_toast),
+ Toast.LENGTH_LONG).show();
+ }
+ }
}
}
@Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_NOT_FIRST_CREATE, true);
+ }
+
+ @Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
* Adjust the UI if the enabled print services changed.
*/
private synchronized void onPrintServicesUpdate() {
- updateServicesWithAddPrinterActivity();
updateEmptyView((DestinationAdapter)mListView.getAdapter());
invalidateOptionsMenu();
}
- /**
- * Register listener for changes to the enabled print services.
- */
- private void registerServiceMonitor() {
- // Listen for services getting disabled
- mPrintServicesDisabledObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- onPrintServicesUpdate();
- }
- };
-
- // Listen for services getting installed or uninstalled
- mPackageObserver = new PackageMonitor() {
- @Override
- public void onPackageModified(String packageName) {
- onPrintServicesUpdate();
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- onPrintServicesUpdate();
- }
-
- @Override
- public void onPackageAdded(String packageName, int uid) {
- onPrintServicesUpdate();
- }
- };
-
- getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.DISABLED_PRINT_SERVICES), false,
- mPrintServicesDisabledObserver);
-
- mPackageObserver.register(this, getMainLooper(), false);
- }
-
- /**
- * Unregister the listeners for changes to the enabled print services.
- */
- private void unregisterServiceMonitorIfNeeded() {
- getContentResolver().unregisterContentObserver(mPrintServicesDisabledObserver);
- mPackageObserver.unregister();
- }
-
@Override
public void onStart() {
super.onStart();
- registerServiceMonitor();
onPrintServicesUpdate();
}
@Override
public void onStop() {
- unregisterServiceMonitorIfNeeded();
super.onStop();
}
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.action_add_printer) {
- showAddPrinterSelectionDialog();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
private void onPrinterSelected(PrinterId printerId) {
Intent intent = new Intent();
intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId);
finish();
}
- private void updateServicesWithAddPrinterActivity() {
- mHasEnabledPrintServices = true;
- mAddPrinterServices.clear();
-
- // Get all enabled print services.
- PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
- List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
-
- // No enabled print services - done.
- if (enabledServices.isEmpty()) {
- mHasEnabledPrintServices = false;
- return;
- }
-
- // Find the services with valid add printers activities.
- final int enabledServiceCount = enabledServices.size();
- for (int i = 0; i < enabledServiceCount; i++) {
- PrintServiceInfo enabledService = enabledServices.get(i);
-
- // No add printers activity declared - next.
- if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
- continue;
- }
-
- ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
- ComponentName addPrintersComponentName = new ComponentName(
- serviceInfo.packageName, enabledService.getAddPrintersActivityName());
- Intent addPritnersIntent = new Intent()
- .setComponent(addPrintersComponentName);
-
- // The add printers activity is valid - add it.
- PackageManager pm = getPackageManager();
- List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0);
- if (!resolvedActivities.isEmpty()) {
- // The activity is a component name, therefore it is one or none.
- ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo;
- if (activityInfo.exported
- && (activityInfo.permission == null
- || pm.checkPermission(activityInfo.permission, getPackageName())
- == PackageManager.PERMISSION_GRANTED)) {
- mAddPrinterServices.add(enabledService);
- }
- }
- }
- }
-
- private void showAddPrinterSelectionDialog() {
- FragmentTransaction transaction = getFragmentManager().beginTransaction();
- Fragment oldFragment = getFragmentManager().findFragmentByTag(
- FRAGMENT_TAG_ADD_PRINTER_DIALOG);
- if (oldFragment != null) {
- transaction.remove(oldFragment);
- }
- AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
- Bundle arguments = new Bundle();
- arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS,
- mAddPrinterServices);
- newFragment.setArguments(arguments);
- transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG);
- transaction.commit();
- }
-
public void updateEmptyView(DestinationAdapter adapter) {
if (mListView.getEmptyView() == null) {
View emptyView = findViewById(R.id.empty_print_state);
}
}
- public static class AddPrinterAlertDialogFragment extends DialogFragment {
-
- private String mAddPrintServiceItem;
-
- @Override
- @SuppressWarnings("unchecked")
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.choose_print_service);
-
- final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
- getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS);
-
- final ArrayAdapter<String> adapter = new ArrayAdapter<>(
- getActivity(), android.R.layout.simple_list_item_1);
- final int printServiceCount = printServices.size();
- for (int i = 0; i < printServiceCount; i++) {
- PrintServiceInfo printService = printServices.get(i);
- adapter.add(printService.getResolveInfo().loadLabel(
- getActivity().getPackageManager()).toString());
- }
+ @Override
+ public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
+ return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this,
+ PrintManager.ENABLED_SERVICES);
+ }
- final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
- Settings.Secure.PRINT_SERVICE_SEARCH_URI);
- final Intent viewIntent;
- if (!TextUtils.isEmpty(searchUri)) {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
- if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
- viewIntent = intent;
- mAddPrintServiceItem = getString(R.string.add_print_service_label);
- adapter.add(mAddPrintServiceItem);
- } else {
- viewIntent = null;
- }
- } else {
- viewIntent = null;
- }
+ @Override
+ public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
+ List<PrintServiceInfo> data) {
+ if (data == null || data.isEmpty()) {
+ mHasEnabledPrintServices = false;
+ } else {
+ mHasEnabledPrintServices = true;
+ }
- builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String item = adapter.getItem(which);
- if (item.equals(mAddPrintServiceItem)) {
- try {
- startActivity(viewIntent);
- } catch (ActivityNotFoundException anfe) {
- Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
- }
- } else {
- PrintServiceInfo printService = printServices.get(which);
- ComponentName componentName = new ComponentName(
- printService.getResolveInfo().serviceInfo.packageName,
- printService.getAddPrintersActivityName());
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(componentName);
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException anfe) {
- Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
- }
- }
- }
- });
+ onPrintServicesUpdate();
+ }
- return builder.create();
+ @Override
+ public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
+ if (!isFinishing()) {
+ onLoadFinished(loader, null);
}
}
@Override
public int getCount() {
synchronized (mLock) {
- return mFilteredPrinters.size();
+ if (mFilteredPrinters.isEmpty()) {
+ return 0;
+ } else {
+ // Add "add printer" item to the end of the list. If the list is empty there is
+ // a link on the empty view
+ return mFilteredPrinters.size() + 1;
+ }
+ }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ // Use separate view types for the "add printer" item an the items referring to printers
+ if (getItem(position) == null) {
+ return 0;
+ } else {
+ return 1;
}
}
@Override
public Object getItem(int position) {
synchronized (mLock) {
- return mFilteredPrinters.get(position);
+ if (position < mFilteredPrinters.size()) {
+ return mFilteredPrinters.get(position);
+ } else {
+ // Return null to mark this as the "add printer item"
+ return null;
+ }
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
+ final PrinterInfo printer = (PrinterInfo) getItem(position);
+
+ // Handle "add printer item"
+ if (printer == null) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.add_printer_list_item,
+ parent, false);
+ }
+
+ return convertView;
+ }
+
if (convertView == null) {
convertView = getLayoutInflater().inflate(
R.layout.printer_list_item, parent, false);
convertView.setEnabled(isActionable(position));
- final PrinterInfo printer = (PrinterInfo) getItem(position);
CharSequence title = printer.getName();
Drawable icon = printer.loadIcon(SelectPrinterActivity.this);
subtitleView.setVisibility(View.GONE);
}
- ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info);
+ LinearLayout moreInfoView = (LinearLayout) convertView.findViewById(R.id.more_info);
if (printer.getInfoIntent() != null) {
moreInfoView.setVisibility(View.VISIBLE);
moreInfoView.setOnClickListener(new OnClickListener() {
public boolean isActionable(int position) {
PrinterInfo printer = (PrinterInfo) getItem(position);
- return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+
+ if (printer == null) {
+ return true;
+ } else {
+ return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+ }
}
}
+++ /dev/null
-/*
- * Copyright (C) 2014 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.printspooler.util;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
-import android.print.PrintManager;
-import android.printservice.PrintServiceInfo;
-
-import java.util.List;
-
-public class PrintOptionUtils {
-
- private PrintOptionUtils() {
- /* ignore - hide constructor */
- }
-
- /**
- * Gets the advanced options activity name for a print service.
- *
- * @param context Context for accessing system resources.
- * @param serviceName The print service name.
- * @return The advanced options activity name or null.
- */
- public static String getAdvancedOptionsActivityName(Context context,
- ComponentName serviceName) {
- PrintManager printManager = (PrintManager) context.getSystemService(
- Context.PRINT_SERVICE);
- List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices();
- final int printServiceCount = printServices.size();
- for (int i = 0; i < printServiceCount; i ++) {
- PrintServiceInfo printServiceInfo = printServices.get(i);
- ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo;
- if (serviceInfo.name.equals(serviceName.getClassName())
- && serviceInfo.packageName.equals(serviceName.getPackageName())) {
- return printServiceInfo.getAdvancedOptionsActivityName();
- }
- }
- return null;
- }
-}
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
/**
- * @return the set of enabled accessibility services. If there are not services
- * it returned the unmodifiable {@link Collections#emptySet()}.
+ * @return the set of enabled accessibility services. If there are no services,
+ * it returns the unmodifiable {@link Collections#emptySet()}.
*/
public static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
- final String enabledServicesSetting = Settings.Secure.getString(
- context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ return getEnabledServicesFromSettings(context, UserHandle.myUserId());
+ }
+
+ /**
+ * @return the set of enabled accessibility services for {@param userId}. If there are no
+ * services, it returns the unmodifiable {@link Collections#emptySet()}.
+ */
+ public static Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+ final String enabledServicesSetting = Settings.Secure.getStringForUser(
+ context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
if (enabledServicesSetting == null) {
return Collections.emptySet();
}
return langContext.getText(resId);
}
+ /**
+ * Changes an accessibility component's state.
+ */
public static void setAccessibilityServiceState(Context context, ComponentName toggledService,
boolean enabled) {
+ setAccessibilityServiceState(context, toggledService, enabled, UserHandle.myUserId());
+ }
+
+ /**
+ * Changes an accessibility component's state for {@param userId}.
+ */
+ public static void setAccessibilityServiceState(Context context, ComponentName toggledService,
+ boolean enabled, int userId) {
// Parse the enabled services.
Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
- context);
+ context, userId);
if (enabledServices.isEmpty()) {
enabledServices = new ArraySet<>(1);
if (enabledServicesBuilderLength > 0) {
enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
}
- Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- enabledServicesBuilder.toString());
+ enabledServicesBuilder.toString(), userId);
// Update accessibility enabled.
- Settings.Secure.putInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0,
+ userId);
}
private static Set<ComponentName> getInstalledServices(Context context) {
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
-import android.app.SynchronousUserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups = new HashMap<>();
private final TextServicesSettings mSettings;
- public void systemRunning() {
- if (!mSystemReady) {
- mSystemReady = true;
+ public static final class Lifecycle extends SystemService {
+ private TextServicesManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new TextServicesManagerService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.TEXT_SERVICES_MANAGER_SERVICE, mService);
+ }
+
+ @Override
+ public void onSwitchUser(@UserIdInt int userHandle) {
+ // Called on the system server's main looper thread.
+ // TODO: Dispatch this to a worker thread as needed.
+ mService.onSwitchUser(userHandle);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ // Called on the system server's main looper thread.
+ // TODO: Dispatch this to a worker thread as needed.
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mService.systemRunning();
+ }
+ }
+ }
+
+ void systemRunning() {
+ synchronized (mSpellCheckerMap) {
+ if (!mSystemReady) {
+ mSystemReady = true;
+ }
+ }
+ }
+
+ void onSwitchUser(@UserIdInt int userId) {
+ synchronized (mSpellCheckerMap) {
+ switchUserLocked(userId);
}
}
int userId = UserHandle.USER_SYSTEM;
try {
- ActivityManagerNative.getDefault().registerUserSwitchObserver(
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) throws RemoteException {
- synchronized(mSpellCheckerMap) {
- switchUserLocked(newUserId);
- }
- }
-
- @Override
- public void onUserSwitchComplete(int newUserId) throws RemoteException {
- }
-
- @Override
- public void onForegroundProfileSwitch(int newProfileId) {
- // Ignore.
- }
- });
userId = ActivityManagerNative.getDefault().getCurrentUser().id;
} catch (RemoteException e) {
Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
switchUserLocked(userId);
}
- private void switchUserLocked(int userId) {
+ private void switchUserLocked(@UserIdInt int userId) {
mSettings.setCurrentUserId(userId);
updateCurrentProfileIds();
unbindServiceLocked();
private static class TextServicesSettings {
private final ContentResolver mResolver;
+ @UserIdInt
private int mCurrentUserId;
@GuardedBy("mLock")
private int[] mCurrentProfileIds = new int[0];
private Object mLock = new Object();
- public TextServicesSettings(ContentResolver resolver, int userId) {
+ public TextServicesSettings(ContentResolver resolver, @UserIdInt int userId) {
mResolver = resolver;
mCurrentUserId = userId;
}
- public void setCurrentUserId(int userId) {
+ public void setCurrentUserId(@UserIdInt int userId) {
if (DBG) {
Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
+ userId + ", new ime = " + getSelectedSpellChecker());
}
}
- public boolean isCurrentProfile(int userId) {
+ public boolean isCurrentProfile(@UserIdInt int userId) {
synchronized (mLock) {
if (userId == mCurrentUserId) return true;
for (int i = 0; i < mCurrentProfileIds.length; i++) {
}
}
+ @UserIdInt
public int getCurrentUserId() {
return mCurrentUserId;
}
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
+import android.print.PrintManager;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.provider.MediaStore;
// Print Spooler
PackageParser.Package printSpoolerPackage = getSystemPackageLPr(
- "com.android.printspooler");
+ PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
if (printSpoolerPackage != null
&& doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
class WindowList extends ArrayList<WindowState> {
+ WindowList() {}
+ WindowList(WindowList windowList) {
+ super(windowList);
+ }
}
/**
+ " remove=" + mWin.mRemoveOnExit
+ " windowAnimating=" + isWindowAnimating());
- final int N = mWin.mChildWindows.size();
- for (int i=0; i<N; i++) {
- mWin.mChildWindows.get(i).mWinAnimator.finishExit();
+ if (!mWin.mChildWindows.isEmpty()) {
+ // Copying to a different list as multiple children can be removed.
+ final WindowList childWindows = new WindowList(mWin.mChildWindows);
+ for (int i = childWindows.size() - 1; i >= 0; i--) {
+ childWindows.get(i).mWinAnimator.finishExit();
+ }
}
if (mEnteringAnimation) {
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
- TextServicesManagerService tsms = null;
ILockSettings lockSettings = null;
AssetAtlasService atlas = null;
MediaRouterService mediaRouter = null;
}
if (!disableNonCoreServices) {
- traceBeginAndSlog("StartTextServicesManagerService");
- try {
- tsms = new TextServicesManagerService(context);
- ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms);
- } catch (Throwable e) {
- reportWtf("starting Text Service Manager Service", e);
- }
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
}
if (!disableNetwork) {
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
- final TextServicesManagerService textServiceManagerServiceF = tsms;
final StatusBarManagerService statusBarF = statusBar;
final AssetAtlasService atlasF = atlas;
final InputManagerService inputManagerF = inputManager;
reportWtf("Notifying CommonTimeManagementService running", e);
}
try {
- if (textServiceManagerServiceF != null)
- textServiceManagerServiceF.systemRunning();
- } catch (Throwable e) {
- reportWtf("Notifying TextServicesManagerService running", e);
- }
- try {
if (atlasF != null) atlasF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying AssetAtlasService running", e);
import android.print.IPrintDocumentAdapter;
import android.print.IPrintJobStateChangeListener;
import android.print.IPrintManager;
+import android.print.IPrintServicesChangeListener;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintAttributes;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
+import android.print.PrintManager;
import android.print.PrinterId;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
+import android.util.Log;
import android.util.SparseArray;
import com.android.internal.content.PackageMonitor;
* PrintManager implementation is contained within.
*/
public final class PrintManagerService extends SystemService {
+ private static final String LOG_TAG = "PrintManagerService";
+
private final PrintManagerImpl mPrintManagerImpl;
public PrintManagerService(Context context) {
}
@Override
- public List<PrintServiceInfo> getEnabledPrintServices(int userId) {
+ public List<PrintServiceInfo> getPrintServices(int selectionFlags, int userId) {
+ Preconditions.checkFlagsArgument(selectionFlags,
+ PrintManager.DISABLED_SERVICES | PrintManager.ENABLED_SERVICES);
+
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final UserState userState;
synchronized (mLock) {
return null;
}
userState = getOrCreateUserStateLocked(resolvedUserId);
-
- // The user state might be updated via the same observer-set as the caller of this
- // interface. If the caller is called back first the user state is not yet updated
- // and the user gets and inconsistent view. Hence force an update.
- userState.updateIfNeededLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
- return userState.getEnabledPrintServices();
+ return userState.getPrintServices(selectionFlags);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public List<PrintServiceInfo> getInstalledPrintServices(int userId) {
+ public void setPrintServiceEnabled(ComponentName service, boolean isEnabled, int userId) {
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+
+ try {
+ if (appId != Process.SYSTEM_UID && appId != UserHandle.getAppId(
+ mContext.getPackageManager().getPackageUidAsUser(
+ PrintManager.PRINT_SPOOLER_PACKAGE_NAME, resolvedUserId))) {
+ throw new SecurityException("Only system and print spooler can call this");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Could not verify caller", e);
+ return;
+ }
+
+ service = Preconditions.checkNotNull(service);
+
final UserState userState;
synchronized (mLock) {
- // Only the current group members can get installed services.
+ // Only the current group members can enable / disable services.
if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
- return null;
+ return;
}
userState = getOrCreateUserStateLocked(resolvedUserId);
}
final long identity = Binder.clearCallingIdentity();
try {
- return userState.getInstalledPrintServices();
+ userState.setPrintServiceEnabled(service, isEnabled);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
+ public void addPrintServicesChangeListener(IPrintServicesChangeListener listener,
+ int userId) throws RemoteException {
+ listener = Preconditions.checkNotNull(listener);
+
+ final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+ final UserState userState;
+ synchronized (mLock) {
+ // Only the current group members can add a print services listener.
+ if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
+ return;
+ }
+ userState = getOrCreateUserStateLocked(resolvedUserId);
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ userState.addPrintServicesChangeListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removePrintServicesChangeListener(IPrintServicesChangeListener listener,
+ int userId) {
+ listener = Preconditions.checkNotNull(listener);
+
+ final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+ final UserState userState;
+ synchronized (mLock) {
+ // Only the current group members can remove a print job listener.
+ if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
+ return;
+ }
+ userState = getOrCreateUserStateLocked(resolvedUserId);
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ userState.removePrintServicesChangeListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
fd = Preconditions.checkNotNull(fd);
pw = Preconditions.checkNotNull(pw);
UserState userState = getOrCreateUserStateLocked(getChangingUserId());
List<PrintServiceInfo> installedServices = userState
- .getInstalledPrintServices();
+ .getPrintServices(PrintManager.ALL_SERVICES);
final int numInstalledServices = installedServices.size();
for (int i = 0; i < numInstalledServices; i++) {
if (installedServices.get(i).getResolveInfo().serviceInfo.packageName
boolean stoppedSomePackages = false;
List<PrintServiceInfo> enabledServices = userState
- .getEnabledPrintServices();
+ .getPrintServices(PrintManager.ENABLED_SERVICES);
if (enabledServices == null) {
return false;
}
import android.print.IPrintSpoolerClient;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
+import android.print.PrintManager;
import android.print.PrinterId;
import android.printservice.PrintService;
import android.util.Slog;
mCallbacks = callbacks;
mClient = new PrintSpoolerClient(this);
mIntent = new Intent();
- mIntent.setComponent(new ComponentName("com.android.printspooler",
- "com.android.printspooler.model.PrintSpoolerService"));
+ mIntent.setComponent(new ComponentName(PrintManager.PRINT_SPOOLER_PACKAGE_NAME,
+ PrintManager.PRINT_SPOOLER_PACKAGE_NAME + ".model.PrintSpoolerService"));
}
public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,
import android.os.UserHandle;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintJobStateChangeListener;
+import android.print.IPrintServicesChangeListener;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintAttributes;
import android.print.PrintJobId;
private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords;
+ private List<PrintServicesChangeListenerRecord> mPrintServicesChangeListenerRecords;
+
private boolean mDestroyed;
public UserState(Context context, int userId, Object lock) {
mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null);
}
- public @Nullable List<PrintServiceInfo> getEnabledPrintServices() {
+ public @Nullable List<PrintServiceInfo> getPrintServices(int selectionFlags) {
synchronized (mLock) {
- List<PrintServiceInfo> enabledServices = null;
+ List<PrintServiceInfo> selectedServices = null;
final int installedServiceCount = mInstalledServices.size();
for (int i = 0; i < installedServiceCount; i++) {
PrintServiceInfo installedService = mInstalledServices.get(i);
+
ComponentName componentName = new ComponentName(
installedService.getResolveInfo().serviceInfo.packageName,
installedService.getResolveInfo().serviceInfo.name);
- if (mActiveServices.containsKey(componentName)) {
- if (enabledServices == null) {
- enabledServices = new ArrayList<PrintServiceInfo>();
+
+ // Update isEnabled under the same lock the final returned list is created
+ installedService.setIsEnabled(mActiveServices.containsKey(componentName));
+
+ if (installedService.isEnabled()) {
+ if ((selectionFlags & PrintManager.ENABLED_SERVICES) == 0) {
+ continue;
}
- enabledServices.add(installedService);
+ } else {
+ if ((selectionFlags & PrintManager.DISABLED_SERVICES) == 0) {
+ continue;
+ }
+ }
+
+ if (selectedServices == null) {
+ selectedServices = new ArrayList<>();
}
+ selectedServices.add(installedService);
}
- return enabledServices;
+ return selectedServices;
}
}
- public List<PrintServiceInfo> getInstalledPrintServices() {
+ public void setPrintServiceEnabled(@NonNull ComponentName serviceName, boolean isEnabled) {
synchronized (mLock) {
- return mInstalledServices;
+ boolean isChanged = false;
+ if (isEnabled) {
+ isChanged = mDisabledServices.remove(serviceName);
+ } else {
+ // Make sure to only disable services that are currently installed
+ final int numServices = mInstalledServices.size();
+ for (int i = 0; i < numServices; i++) {
+ PrintServiceInfo service = mInstalledServices.get(i);
+
+ if (service.getComponentName().equals(serviceName)) {
+ mDisabledServices.add(serviceName);
+ isChanged = true;
+ break;
+ }
+ }
+ }
+
+ if (isChanged) {
+ writeDisabledPrintServicesLocked(mDisabledServices);
+
+ onConfigurationChangedLocked();
+ }
}
}
}
}
+ public void addPrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener)
+ throws RemoteException {
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mPrintServicesChangeListenerRecords == null) {
+ mPrintServicesChangeListenerRecords = new ArrayList<>();
+ }
+ mPrintServicesChangeListenerRecords.add(
+ new PrintServicesChangeListenerRecord(listener) {
+ @Override
+ public void onBinderDied() {
+ mPrintServicesChangeListenerRecords.remove(this);
+ }
+ });
+ }
+ }
+
+ public void removePrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) {
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mPrintServicesChangeListenerRecords == null) {
+ return;
+ }
+ final int recordCount = mPrintServicesChangeListenerRecords.size();
+ for (int i = 0; i < recordCount; i++) {
+ PrintServicesChangeListenerRecord record =
+ mPrintServicesChangeListenerRecords.get(i);
+ if (record.listener.asBinder().equals(listener.asBinder())) {
+ mPrintServicesChangeListenerRecords.remove(i);
+ break;
+ }
+ }
+ if (mPrintServicesChangeListenerRecords.isEmpty()) {
+ mPrintServicesChangeListenerRecords = null;
+ }
+ }
+ }
+
@Override
public void onPrintJobStateChanged(PrintJobInfo printJob) {
mPrintJobForAppCache.onPrintJobStateChanged(printJob);
printJob.getAppId(), 0, printJob.getId()).sendToTarget();
}
+ public void onPrintServicesChanged() {
+ mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_SERVICES_CHANGED).sendToTarget();
+ }
+
@Override
public void onPrintersAdded(List<PrinterInfo> printers) {
synchronized (mLock) {
iterator.remove();
}
}
+
+ onPrintServicesChanged();
}
private void addServiceLocked(RemotePrintService service) {
}
}
+ private void handleDispatchPrintServicesChanged() {
+ final List<PrintServicesChangeListenerRecord> records;
+ synchronized (mLock) {
+ if (mPrintServicesChangeListenerRecords == null) {
+ return;
+ }
+ records = new ArrayList<>(mPrintServicesChangeListenerRecords);
+ }
+ final int recordCount = records.size();
+ for (int i = 0; i < recordCount; i++) {
+ PrintServicesChangeListenerRecord record = records.get(i);
+
+ try {
+ record.listener.onPrintServicesChanged();;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error notifying for print services change", re);
+ }
+ }
+ }
+
private final class UserStateHandler extends Handler {
public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1;
+ public static final int MSG_DISPATCH_PRINT_SERVICES_CHANGED = 2;
public UserStateHandler(Looper looper) {
super(looper, null, false);
@Override
public void handleMessage(Message message) {
- if (message.what == MSG_DISPATCH_PRINT_JOB_STATE_CHANGED) {
- PrintJobId printJobId = (PrintJobId) message.obj;
- final int appId = message.arg1;
- handleDispatchPrintJobStateChanged(printJobId, appId);
+ switch (message.what) {
+ case MSG_DISPATCH_PRINT_JOB_STATE_CHANGED:
+ PrintJobId printJobId = (PrintJobId) message.obj;
+ final int appId = message.arg1;
+ handleDispatchPrintJobStateChanged(printJobId, appId);
+ break;
+ case MSG_DISPATCH_PRINT_SERVICES_CHANGED:
+ handleDispatchPrintServicesChanged();
+ break;
+ default:
+ // not reached
}
}
}
public abstract void onBinderDied();
}
+ private abstract class PrintServicesChangeListenerRecord implements DeathRecipient {
+ @NonNull final IPrintServicesChangeListener listener;
+
+ public PrintServicesChangeListenerRecord(@NonNull IPrintServicesChangeListener listener) throws RemoteException {
+ this.listener = listener;
+ listener.asBinder().linkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ listener.asBinder().unlinkToDeath(this, 0);
+ onBinderDied();
+ }
+
+ public abstract void onBinderDied();
+ }
+
private class PrinterDiscoverySessionMediator {
private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
new ArrayMap<PrinterId, PrinterInfo>();
*/
public final class BridgeTypedArray extends TypedArray {
- private final BridgeResources mBridgeResources;
+ private final Resources mBridgeResources;
private final BridgeContext mContext;
private final boolean mPlatformFile;
@Nullable
private int[] mEmptyIds;
- public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len,
+ public BridgeTypedArray(Resources resources, BridgeContext context, int len,
boolean platformFile) {
super(resources, null, null, 0);
mBridgeResources = resources;
if (resVal instanceof ArrayResourceValue) {
ArrayResourceValue array = (ArrayResourceValue) resVal;
int count = array.getElementCount();
- return count >= 0 ? mBridgeResources.fillValues(array, new CharSequence[count]) : null;
+ return count >= 0 ? Resources_Delegate.fillValues(mBridgeResources, array, new CharSequence[count]) :
+ null;
}
int id = getResourceId(index, 0);
String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
}
static TypedArray obtain(Resources res, int len) {
- return res instanceof BridgeResources ?
- new BridgeTypedArray(((BridgeResources) res), null, len, true) : null;
+ return new BridgeTypedArray(res, null, len, true);
}
}
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.layoutlib.bridge.util.NinePatchInputStream;
import com.android.ninepatch.NinePatch;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import java.util.Iterator;
@SuppressWarnings("deprecation")
-public final class BridgeResources extends Resources {
+public class Resources_Delegate {
- private BridgeContext mContext;
- private LayoutlibCallback mLayoutlibCallback;
- private boolean[] mPlatformResourceFlag = new boolean[1];
- private TypedValue mTmpValue = new TypedValue();
+ private static boolean[] mPlatformResourceFlag = new boolean[1];
- /**
- * Simpler wrapper around FileInputStream. This is used when the input stream represent
- * not a normal bitmap but a nine patch.
- * This is useful when the InputStream is created in a method but used in another that needs
- * to know whether this is 9-patch or not, such as BitmapFactory.
- */
- public class NinePatchInputStream extends FileInputStream {
- private boolean mFakeMarkSupport = true;
- public NinePatchInputStream(File file) throws FileNotFoundException {
- super(file);
- }
-
- @Override
- public boolean markSupported() {
- //noinspection SimplifiableIfStatement
- if (mFakeMarkSupport) {
- // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
- return true;
- }
-
- return super.markSupported();
- }
-
- public void disableFakeMarkSupport() {
- // disable fake mark support so that in case codec actually try to use them
- // we don't lie to them.
- mFakeMarkSupport = false;
- }
- }
-
- /**
- * This initializes the static field {@link Resources#mSystem} which is used
- * by methods who get global resources using {@link Resources#getSystem()}.
- * <p/>
- * They will end up using our bridge resources.
- * <p/>
- * {@link Bridge} calls this method after setting up a new bridge.
- */
public static Resources initSystem(BridgeContext context,
AssetManager assets,
DisplayMetrics metrics,
Configuration config,
LayoutlibCallback layoutlibCallback) {
- return Resources.mSystem = new BridgeResources(context,
- assets,
- metrics,
- config,
- layoutlibCallback);
+ Resources resources = new Resources(assets, metrics, config);
+ resources.mContext = context;
+ resources.mLayoutlibCallback = layoutlibCallback;
+ return Resources.mSystem = resources;
}
/**
- * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects
- * around that would prevent us from unloading the library.
+ * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that
+ * would prevent us from unloading the library.
*/
public static void disposeSystem() {
- if (Resources.mSystem instanceof BridgeResources) {
- ((BridgeResources)(Resources.mSystem)).mContext = null;
- ((BridgeResources)(Resources.mSystem)).mLayoutlibCallback = null;
- }
+ Resources.mSystem.mContext = null;
+ Resources.mSystem.mLayoutlibCallback = null;
Resources.mSystem = null;
}
- private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
- Configuration config, LayoutlibCallback layoutlibCallback) {
- super(assets, metrics, config);
- mContext = context;
- mLayoutlibCallback = layoutlibCallback;
- }
-
- public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) {
- return new BridgeTypedArray(this, mContext, numEntries, platformFile);
+ public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
+ boolean platformFile) {
+ return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile);
}
- private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) {
+ private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+ boolean[] platformResFlag_out) {
// first get the String related to this id in the framework
Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+ // Set the layoutlib callback and context for resources
+ if (resources != Resources.mSystem && resources.mLayoutlibCallback == null) {
+ resources.mLayoutlibCallback = Resources.mSystem.mLayoutlibCallback;
+ resources.mContext = Resources.mSystem.mContext;
+ }
+
if (resourceInfo != null) {
platformResFlag_out[0] = true;
String attributeName = resourceInfo.getSecond();
- return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource(
- resourceInfo.getFirst(), attributeName));
+ return Pair.of(attributeName,
+ resources.mContext.getRenderResources().getFrameworkResource(
+ resourceInfo.getFirst(), attributeName));
}
// didn't find a match in the framework? look in the project.
- if (mLayoutlibCallback != null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(id);
+ if (resources.mLayoutlibCallback != null) {
+ resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
if (resourceInfo != null) {
platformResFlag_out[0] = false;
String attributeName = resourceInfo.getSecond();
- return Pair.of(attributeName, mContext.getRenderResources().getProjectResource(
- resourceInfo.getFirst(), attributeName));
+ return Pair.of(attributeName,
+ resources.mContext.getRenderResources().getProjectResource(
+ resourceInfo.getFirst(), attributeName));
}
}
return null;
}
- @Override
- public Drawable getDrawable(int id, Theme theme) {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static Drawable getDrawable(Resources resources, int id) {
+ return getDrawable(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static Drawable getDrawable(Resources resources, int id, Theme theme) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
- return ResourceHelper.getDrawable(value.getSecond(), mContext, theme);
+ return ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme);
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public int getColor(int id, Theme theme) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static int getColor(Resources resources, int id) {
+ return getColor(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resourceValue = value.getSecond();
String message;
if (new File(resourceValue.getValue()).isFile()) {
String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/"
- + resourceValue.getName();
+ + resourceValue.getName();
message = "Hexadecimal color expected, found Color State List for " + resource;
} else {
message = e.getMessage();
// Suppress possible NPE. getColorStateList will never return null, it will instead
// throw an exception, but intelliJ can't figure that out
//noinspection ConstantConditions
- return getColorStateList(id, theme).getDefaultColor();
+ return getColorStateList(resources, id, theme).getDefaultColor();
}
- @Override
- public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
- Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
+ return getColorStateList(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static ColorStateList getColorStateList(Resources resources, int id, Theme theme)
+ throws NotFoundException {
+ Pair<String, ResourceValue> resValue =
+ getResourceValue(resources, id, mPlatformResourceFlag);
if (resValue != null) {
ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
- mContext);
+ resources.mContext);
if (stateList != null) {
return stateList.obtainForTheme(theme);
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public CharSequence getText(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static CharSequence getText(Resources resources, int id, CharSequence def) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ return def;
+ }
+
+ @LayoutlibDelegate
+ static CharSequence getText(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public CharSequence[] getTextArray(int id) throws NotFoundException {
- ResourceValue resValue = getArrayResourceValue(id);
+ @LayoutlibDelegate
+ static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(resources, id);
if (resValue == null) {
// Error already logged by getArrayResourceValue.
return new CharSequence[0];
} else if (!(resValue instanceof ArrayResourceValue)) {
return new CharSequence[]{
- resolveReference(resValue.getValue(), resValue.isFramework())};
+ resolveReference(resources, resValue.getValue(), resValue.isFramework())};
}
ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(arv, new CharSequence[arv.getElementCount()]);
+ return fillValues(resources, arv, new CharSequence[arv.getElementCount()]);
}
- @Override
- public String[] getStringArray(int id) throws NotFoundException {
- ResourceValue resValue = getArrayResourceValue(id);
+ @LayoutlibDelegate
+ static String[] getStringArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(resources, id);
if (resValue == null) {
// Error already logged by getArrayResourceValue.
return new String[0];
} else if (!(resValue instanceof ArrayResourceValue)) {
return new String[]{
- resolveReference(resValue.getValue(), resValue.isFramework())};
+ resolveReference(resources, resValue.getValue(), resValue.isFramework())};
}
ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(arv, new String[arv.getElementCount()]);
+ return fillValues(resources, arv, new String[arv.getElementCount()]);
}
/**
* always Strings. The ideal signature for the method should be <T super String>, but java
* generics don't support it.
*/
- <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) {
+ static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue,
+ T[] values) {
int i = 0;
for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
@SuppressWarnings("unchecked")
- T s = (T) resolveReference(iterator.next(), resValue.isFramework());
+ T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework());
values[i] = s;
}
return values;
}
- @Override
- public int[] getIntArray(int id) throws NotFoundException {
- ResourceValue rv = getArrayResourceValue(id);
+ @LayoutlibDelegate
+ static int[] getIntArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue rv = getArrayResourceValue(resources, id);
if (rv == null) {
// Error already logged by getArrayResourceValue.
return new int[0];
} else if (!(rv instanceof ArrayResourceValue)) {
// This is an older IDE that can only give us the first element of the array.
- String firstValue = resolveReference(rv.getValue(), rv.isFramework());
+ String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework());
try {
return new int[]{getInt(firstValue)};
} catch (NumberFormatException e) {
int[] values = new int[resValue.getElementCount()];
int i = 0;
for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
- String element = resolveReference(iterator.next(), resValue.isFramework());
+ String element = resolveReference(resources, iterator.next(), resValue.isFramework());
try {
values[i] = getInt(element);
} catch (NumberFormatException e) {
* method returns the ResourceValue. This happens on older versions of the IDE, which did not
* parse the array resources properly.
* <p/>
+ *
* @throws NotFoundException if no resource if found
*/
@Nullable
- private ResourceValue getArrayResourceValue(int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+ private static ResourceValue getArrayResourceValue(Resources resources, int id)
+ throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
if (v != null) {
ResourceValue resValue = v.getSecond();
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
@NonNull
- private String resolveReference(@NonNull String ref, boolean forceFrameworkOnly) {
+ private static String resolveReference(Resources resources, @NonNull String ref,
+ boolean forceFrameworkOnly) {
if (ref.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || ref.startsWith
(SdkConstants.PREFIX_THEME_REF)) {
ResourceValue rv =
- mContext.getRenderResources().findResValue(ref, forceFrameworkOnly);
- rv = mContext.getRenderResources().resolveResValue(rv);
+ resources.mContext.getRenderResources().findResValue(ref, forceFrameworkOnly);
+ rv = resources.mContext.getRenderResources().resolveResValue(rv);
if (rv != null) {
return rv.getValue();
} else {
return ref;
}
- @Override
- public XmlResourceParser getLayout(int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
if (v != null) {
ResourceValue value = v.getSecond();
try {
// check if the current parser can provide us with a custom parser.
- if (mPlatformResourceFlag[0] == false) {
- parser = mLayoutlibCallback.getParser(value);
+ if (!mPlatformResourceFlag[0]) {
+ parser = resources.mLayoutlibCallback.getParser(value);
}
// create a new one manually if needed.
}
if (parser != null) {
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext,
+ mPlatformResourceFlag[0]);
}
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public XmlResourceParser getAnimation(int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
if (v != null) {
ResourceValue value = v.getSecond();
- XmlPullParser parser = null;
+ XmlPullParser parser;
try {
File xml = new File(value.getValue());
// give that to our XmlBlockParser
parser = ParserFactory.create(xml);
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext,
+ mPlatformResourceFlag[0]);
}
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
- return mContext.obtainStyledAttributes(set, attrs);
+ @LayoutlibDelegate
+ static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
+ return resources.mContext.obtainStyledAttributes(set, attrs);
}
- @Override
- public TypedArray obtainTypedArray(int id) throws NotFoundException {
- throw new UnsupportedOperationException();
+ @LayoutlibDelegate
+ static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet
+ set, int[] attrs) {
+ return Resources.obtainAttributes_Original(resources, theme, set, attrs);
}
+ @LayoutlibDelegate
+ static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
- @Override
- public float getDimension(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static float getDimension(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
} else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
return LayoutParams.WRAP_CONTENT;
}
-
+ TypedValue tmpValue = new TypedValue();
if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
- mTmpValue.type == TypedValue.TYPE_DIMENSION) {
- return mTmpValue.getDimension(getDisplayMetrics());
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return tmpValue.getDimension(resources.getDisplayMetrics());
}
}
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return 0;
}
- @Override
- public int getDimensionPixelOffset(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
+ TypedValue tmpValue = new TypedValue();
if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
- mTmpValue.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(mTmpValue.data,
- getDisplayMetrics());
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(tmpValue.data,
+ resources.getDisplayMetrics());
}
}
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return 0;
}
- @Override
- public int getDimensionPixelSize(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
+ TypedValue tmpValue = new TypedValue();
if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
- mTmpValue.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(mTmpValue.data,
- getDisplayMetrics());
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(tmpValue.data,
+ resources.getDisplayMetrics());
}
}
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return 0;
}
- @Override
- public int getInteger(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static int getInteger(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return 0;
}
- @Override
- public boolean getBoolean(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static boolean getBoolean(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return false;
}
- @Override
- public String getResourceEntryName(int resid) throws NotFoundException {
+ @LayoutlibDelegate
+ static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public String getResourceName(int resid) throws NotFoundException {
+ @LayoutlibDelegate
+ static String getResourceName(Resources resources, int resid) throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public String getResourceTypeName(int resid) throws NotFoundException {
+ @LayoutlibDelegate
+ static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public String getString(int id, Object... formatArgs) throws NotFoundException {
- String s = getString(id);
+ @LayoutlibDelegate
+ static String getString(Resources resources, int id, Object... formatArgs)
+ throws NotFoundException {
+ String s = getString(resources, id);
if (s != null) {
return String.format(s, formatArgs);
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public String getString(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static String getString(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null && value.getSecond().getValue() != null) {
return value.getSecond().getValue();
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ @LayoutlibDelegate
+ static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resVal = value.getSecond();
}
if (resVal instanceof DensityBasedResourceValue) {
outValue.density =
- ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
+ ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
}
// else it's a string
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
}
- @Override
- public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ @LayoutlibDelegate
+ static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public XmlResourceParser getXml(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
String v = value.getSecond().getValue();
try {
XmlPullParser parser = ParserFactory.create(f);
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext,
+ mPlatformResourceFlag[0]);
} catch (XmlPullParserException e) {
NotFoundException newE = new NotFoundException();
newE.initCause(e);
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public XmlResourceParser loadXmlResourceParser(String file, int id,
+ @LayoutlibDelegate
+ static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
+ String type) throws NotFoundException {
+ return resources.loadXmlResourceParser_Original(id, type);
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id,
int assetCookie, String type) throws NotFoundException {
// even though we know the XML file to load directly, we still need to resolve the
// id so that we can know if it's a platform or project resource.
// (mPlatformResouceFlag will get the result and will be used later).
- getResourceValue(id, mPlatformResourceFlag);
+ getResourceValue(resources, id, mPlatformResourceFlag);
File f = new File(file);
try {
XmlPullParser parser = ParserFactory.create(f);
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext, mPlatformResourceFlag[0]);
} catch (XmlPullParserException e) {
NotFoundException newE = new NotFoundException();
newE.initCause(e);
}
}
- @Override
- public InputStream openRawResource(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
String path = value.getSecond().getValue();
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
- getValue(id, value, true);
+ @LayoutlibDelegate
+ static InputStream openRawResource(Resources resources, int id, TypedValue value) throws
+ NotFoundException {
+ getValue(resources, id, value, true);
String path = value.string.toString();
throw new NotFoundException();
}
- @Override
- public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ @LayoutlibDelegate
+ static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws
+ NotFoundException {
throw new UnsupportedOperationException();
}
/**
- * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
+ * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource
+ * type.
+ *
* @param id the id of the resource
+ *
* @throws NotFoundException
*/
- private void throwException(int id) throws NotFoundException {
+ private static void throwException(Resources resources, int id) throws NotFoundException {
// first get the String related to this id in the framework
Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
// if the name is unknown in the framework, get it from the custom view loader.
- if (resourceInfo == null && mLayoutlibCallback != null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(id);
+ if (resourceInfo == null && resources.mLayoutlibCallback != null) {
+ resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
}
String message;
throw new NotFoundException(message);
}
- private int getInt(String v) throws NumberFormatException {
+ private static int getInt(String v) throws NumberFormatException {
int radix = 10;
if (v.startsWith("0x")) {
v = v.substring(2);
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
-import android.content.res.BridgeResources.NinePatchInputStream;
+import com.android.layoutlib.bridge.util.NinePatchInputStream;
import android.graphics.BitmapFactory.Options;
import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
--- /dev/null
+/*
+ * 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 android.graphics.drawable;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper_Delegate;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+import android.graphics.drawable.VectorDrawable_Delegate.VFullPath_Delegate;
+import android.graphics.drawable.VectorDrawable_Delegate.VGroup_Delegate;
+import android.graphics.drawable.VectorDrawable_Delegate.VNativeObject;
+import android.graphics.drawable.VectorDrawable_Delegate.VPathRenderer_Delegate;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link
+ * AnimatedVectorDrawable}
+ * <p>
+ * Through the layoutlib_create tool, the original methods of AnimatedVectorDrawable have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class AnimatedVectorDrawable_Delegate {
+ private static DelegateManager<AnimatorSetHolder> sAnimatorSets = new
+ DelegateManager<>(AnimatorSetHolder.class);
+ private static DelegateManager<PropertySetter> sHolders = new
+ DelegateManager<>(PropertySetter.class);
+
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateAnimatorSet() {
+ return sAnimatorSets.addNewDelegate(new AnimatorSetHolder());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder,
+ long nativeInterpolator, long startDelay, long duration, int repeatCount) {
+ PropertySetter holder = sHolders.getDelegate(propertyValuesHolder);
+ if (holder == null || holder.getValues() == null) {
+ return;
+ }
+
+ ObjectAnimator animator = new ObjectAnimator();
+ animator.setValues(holder.getValues());
+ animator.setInterpolator(
+ NativeInterpolatorFactoryHelper_Delegate.getDelegate(nativeInterpolator));
+ animator.setStartDelay(startDelay);
+ animator.setDuration(duration);
+ animator.setRepeatCount(repeatCount);
+ animator.setTarget(holder);
+ animator.setPropertyName(holder.getValues().getPropertyName());
+
+ AnimatorSetHolder set = sAnimatorSets.getDelegate(setPtr);
+ assert set != null;
+ set.addAnimator(animator);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue) {
+ VGroup_Delegate group = VNativeObject.getDelegate(nativePtr);
+ Consumer<Float> setter = group.getPropertySetter(propertyId);
+
+ return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+ long endValuePtr) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "AnimatedVectorDrawable path " +
+ "animations are not supported.", null, null);
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+ int startValue, int endValue) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
+ Consumer<Integer> setter = path.getIntPropertySetter(propertyId);
+
+ return sHolders.addNewDelegate(IntPropertySetter.of(setter, startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue) {
+ VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
+ Consumer<Float> setter = path.getFloatPropertySetter(propertyId);
+
+ return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+ float endValue) {
+ VPathRenderer_Delegate renderer = VNativeObject.getDelegate(nativePtr);
+
+ return sHolders.addNewDelegate(FloatPropertySetter.of(renderer::setRootAlpha,
+ startValue,
+ endValue));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetPropertyHolderData(long nativePtr, float[] data, int length) {
+ PropertySetter setter = sHolders.getDelegate(nativePtr);
+ assert setter != null;
+
+ setter.setValues(data);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.start();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.reverse();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nEnd(long animatorSetPtr) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.end();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nReset(long animatorSetPtr) {
+ AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
+ assert animatorSet != null;
+
+ animatorSet.end();
+ animatorSet.start();
+ }
+
+ private static class AnimatorSetHolder {
+ private ArrayList<Animator> mAnimators = new ArrayList<>();
+ private AnimatorSet mAnimatorSet = null;
+
+ private void addAnimator(@NonNull Animator animator) {
+ mAnimators.add(animator);
+ }
+
+ private void ensureAnimatorSet() {
+ if (mAnimatorSet == null) {
+ mAnimatorSet = new AnimatorSet();
+ mAnimatorSet.playTogether(mAnimators);
+ }
+ }
+
+ private void start() {
+ ensureAnimatorSet();
+
+ mAnimatorSet.start();
+ }
+
+ private void end() {
+ mAnimatorSet.end();
+ }
+
+ private void reset() {
+ end();
+ start();
+ }
+
+ private void reverse() {
+ mAnimatorSet.reverse();
+ }
+ }
+
+ /**
+ * Class that allows setting a value and holds the range of values for the given property.
+ *
+ * @param <T> the type of the property
+ */
+ private static class PropertySetter<T> {
+ final Consumer<T> mValueSetter;
+ private PropertyValuesHolder mValues;
+
+ private PropertySetter(@NonNull Consumer<T> valueSetter) {
+ mValueSetter = valueSetter;
+ }
+
+ /**
+ * Method to set an {@link Integer} value for this property. The default implementation of
+ * this method doesn't do anything. This method is accessed via reflection by the
+ * PropertyValuesHolder.
+ */
+ public void setIntValue(Integer value) {
+ }
+
+ /**
+ * Method to set an {@link Integer} value for this property. The default implementation of
+ * this method doesn't do anything. This method is accessed via reflection by the
+ * PropertyValuesHolder.
+ */
+ public void setFloatValue(Float value) {
+ }
+
+ void setValues(float... values) {
+ mValues = PropertyValuesHolder.ofFloat("floatValue", values);
+ }
+
+ @Nullable
+ PropertyValuesHolder getValues() {
+ return mValues;
+ }
+
+ void setValues(int... values) {
+ mValues = PropertyValuesHolder.ofInt("intValue", values);
+ }
+ }
+
+ private static class IntPropertySetter extends PropertySetter<Integer> {
+ private IntPropertySetter(Consumer<Integer> valueSetter) {
+ super(valueSetter);
+ }
+
+ private static PropertySetter of(Consumer<Integer> valueSetter, int... values) {
+ PropertySetter setter = new IntPropertySetter(valueSetter);
+ setter.setValues(values);
+
+ return setter;
+ }
+
+ public void setIntValue(Integer value) {
+ mValueSetter.accept(value);
+ }
+ }
+
+ private static class FloatPropertySetter extends PropertySetter<Float> {
+ private FloatPropertySetter(Consumer<Float> valueSetter) {
+ super(valueSetter);
+ }
+
+ private static PropertySetter of(Consumer<Float> valueSetter, float... values) {
+ PropertySetter setter = new FloatPropertySetter(valueSetter);
+ setter.setValues(values);
+
+ return setter;
+ }
+
+ public void setFloatValue(Float value) {
+ mValueSetter.accept(value);
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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 android.graphics.drawable;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+
+public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
+ @LayoutlibDelegate
+ /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) {
+ return true;
+ }
+}
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.annotation.NonNull;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
+import java.util.function.Consumer;
import static android.graphics.Canvas.CLIP_SAVE_FLAG;
import static android.graphics.Canvas.MATRIX_SAVE_FLAG;
private static final DelegateManager<VNativeObject> sPathManager =
new DelegateManager<>(VNativeObject.class);
- private static <T> T getDelegate(long nativePtr) {
- //noinspection unchecked
- T object = (T) sPathManager.getDelegate(nativePtr);
- assert object != null;
-
- return object;
- }
-
/**
* Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
* null.
@LayoutlibDelegate
static long nCreateRenderer(long rootGroupPtr) {
- VGroup_Delegate rootGroup = getDelegate(rootGroupPtr);
+ VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr);
return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup));
}
@LayoutlibDelegate
static void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
float viewportHeight) {
- VPathRenderer_Delegate nativePathRenderer = getDelegate(rendererPtr);
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
nativePathRenderer.mViewportWidth = viewportWidth;
nativePathRenderer.mViewportHeight = viewportHeight;
}
@LayoutlibDelegate
static boolean nSetRootAlpha(long rendererPtr, float alpha) {
- VPathRenderer_Delegate nativePathRenderer = getDelegate(rendererPtr);
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
nativePathRenderer.setRootAlpha(alpha);
return true;
@LayoutlibDelegate
static float nGetRootAlpha(long rendererPtr) {
- VPathRenderer_Delegate nativePathRenderer = getDelegate(rendererPtr);
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
return nativePathRenderer.getRootAlpha();
}
@LayoutlibDelegate
static void nDraw(long rendererPtr, long canvasWrapperPtr,
long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) {
- VPathRenderer_Delegate nativePathRenderer =
- getDelegate(rendererPtr);
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
Canvas_Delegate.native_save(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.left, bounds.top);
@LayoutlibDelegate
static long nCreateFullPath(long nativeFullPathPtr) {
- VFullPath_Delegate original = getDelegate(nativeFullPathPtr);
+ VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
return sPathManager.addNewDelegate(new VFullPath_Delegate(original));
}
@LayoutlibDelegate
static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData,
int length) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
ByteBuffer properties = ByteBuffer.wrap(propertiesData);
properties.order(ByteOrder.nativeOrder());
int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
int strokeLineJoin) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeWidth(strokeWidth);
path.setStrokeColor(strokeColor);
@LayoutlibDelegate
static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setFillGradient(fillGradientPtr);
}
@LayoutlibDelegate
static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeGradient(strokeGradientPtr);
}
@LayoutlibDelegate
static long nCreateClipPath(long clipPathPtr) {
- VClipPath_Delegate original = getDelegate(clipPathPtr);
+ VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
return sPathManager.addNewDelegate(new VClipPath_Delegate(original));
}
@LayoutlibDelegate
static long nCreateGroup(long groupPtr) {
- VGroup_Delegate original = getDelegate(groupPtr);
+ VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
return sPathManager.addNewDelegate(
new VGroup_Delegate(original, new ArrayMap<String, Object>()));
}
@LayoutlibDelegate
static void nSetName(long nodePtr, String name) {
- VNativeObject group = getDelegate(nodePtr);
+ VNativeObject group = VNativeObject.getDelegate(nodePtr);
group.setName(name);
}
@LayoutlibDelegate
static boolean nGetGroupProperties(long groupPtr, float[] propertiesData,
int length) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
FloatBuffer properties = FloatBuffer.wrap(propertiesData);
@LayoutlibDelegate
static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
float pivotY, float scaleX, float scaleY, float translateX, float translateY) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setRotation(rotate);
group.setPivotX(pivotX);
@LayoutlibDelegate
static void nAddChild(long groupPtr, long nodePtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
- group.mChildren.add(getDelegate(nodePtr));
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
+ group.mChildren.add(VNativeObject.getDelegate(nodePtr));
}
@LayoutlibDelegate
static void nSetPathString(long pathPtr, String pathString, int length) {
- VPath_Delegate path = getDelegate(pathPtr);
+ VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString));
}
// Setters and getters during animation.
@LayoutlibDelegate
static float nGetRotation(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getRotation();
}
@LayoutlibDelegate
static void nSetRotation(long groupPtr, float rotation) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setRotation(rotation);
}
@LayoutlibDelegate
static float nGetPivotX(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getPivotX();
}
@LayoutlibDelegate
static void nSetPivotX(long groupPtr, float pivotX) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setPivotX(pivotX);
}
@LayoutlibDelegate
static float nGetPivotY(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getPivotY();
}
@LayoutlibDelegate
static void nSetPivotY(long groupPtr, float pivotY) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setPivotY(pivotY);
}
@LayoutlibDelegate
static float nGetScaleX(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getScaleX();
}
@LayoutlibDelegate
static void nSetScaleX(long groupPtr, float scaleX) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setScaleX(scaleX);
}
@LayoutlibDelegate
static float nGetScaleY(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getScaleY();
}
@LayoutlibDelegate
static void nSetScaleY(long groupPtr, float scaleY) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setScaleY(scaleY);
}
@LayoutlibDelegate
static float nGetTranslateX(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getTranslateX();
}
@LayoutlibDelegate
static void nSetTranslateX(long groupPtr, float translateX) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setTranslateX(translateX);
}
@LayoutlibDelegate
static float nGetTranslateY(long groupPtr) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
return group.getTranslateY();
}
@LayoutlibDelegate
static void nSetTranslateY(long groupPtr, float translateY) {
- VGroup_Delegate group = getDelegate(groupPtr);
+ VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
group.setTranslateY(translateY);
}
@LayoutlibDelegate
static void nSetPathData(long pathPtr, long pathDataPtr) {
- VPath_Delegate path = getDelegate(pathPtr);
+ VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes());
}
@LayoutlibDelegate
static float nGetStrokeWidth(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getStrokeWidth();
}
@LayoutlibDelegate
static void nSetStrokeWidth(long pathPtr, float width) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeWidth(width);
}
@LayoutlibDelegate
static int nGetStrokeColor(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getStrokeColor();
}
@LayoutlibDelegate
static void nSetStrokeColor(long pathPtr, int strokeColor) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeColor(strokeColor);
}
@LayoutlibDelegate
static float nGetStrokeAlpha(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getStrokeAlpha();
}
@LayoutlibDelegate
static void nSetStrokeAlpha(long pathPtr, float alpha) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setStrokeAlpha(alpha);
}
@LayoutlibDelegate
static int nGetFillColor(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getFillColor();
}
@LayoutlibDelegate
static void nSetFillColor(long pathPtr, int fillColor) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setFillColor(fillColor);
}
@LayoutlibDelegate
static float nGetFillAlpha(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getFillAlpha();
}
@LayoutlibDelegate
static void nSetFillAlpha(long pathPtr, float fillAlpha) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setFillAlpha(fillAlpha);
}
@LayoutlibDelegate
static float nGetTrimPathStart(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getTrimPathStart();
}
@LayoutlibDelegate
static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setTrimPathStart(trimPathStart);
}
@LayoutlibDelegate
static float nGetTrimPathEnd(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getTrimPathEnd();
}
@LayoutlibDelegate
static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setTrimPathEnd(trimPathEnd);
}
@LayoutlibDelegate
static float nGetTrimPathOffset(long pathPtr) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
return path.getTrimPathOffset();
}
@LayoutlibDelegate
static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
- VFullPath_Delegate path = getDelegate(pathPtr);
+ VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
path.setTrimPathOffset(trimPathOffset);
}
* not need it
* </ol>
*/
- private interface VNativeObject {
+ interface VNativeObject {
+ @NonNull
+ static <T> T getDelegate(long nativePtr) {
+ //noinspection unchecked
+ T vNativeObject = (T) sPathManager.getDelegate(nativePtr);
+
+ assert vNativeObject != null;
+ return vNativeObject;
+ }
+
void setName(String name);
}
}
}
- private static class VFullPath_Delegate extends VPath_Delegate {
+ static class VFullPath_Delegate extends VPath_Delegate {
// These constants need to be kept in sync with their values in VectorDrawable.VFullPath
private static final int STROKE_WIDTH_INDEX = 0;
private static final int STROKE_COLOR_INDEX = 1;
private static final int LINEJOIN_ROUND = 1;
private static final int LINEJOIN_BEVEL = 2;
+ @NonNull
+ public Consumer<Float> getFloatPropertySetter(int propertyIdx) {
+ switch (propertyIdx) {
+ case STROKE_ALPHA_INDEX:
+ return this::setStrokeAlpha;
+ case FILL_ALPHA_INDEX:
+ return this::setFillAlpha;
+ case TRIM_PATH_START_INDEX:
+ return this::setTrimPathStart;
+ case TRIM_PATH_END_INDEX:
+ return this::setTrimPathEnd;
+ case TRIM_PATH_OFFSET_INDEX:
+ return this::setTrimPathOffset;
+ }
+
+ throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
+ + propertyIdx);
+ }
+
+ @NonNull
+ public Consumer<Integer> getIntPropertySetter(int propertyIdx) {
+ switch (propertyIdx) {
+ case STROKE_COLOR_INDEX:
+ return this::setStrokeColor;
+ case FILL_COLOR_INDEX:
+ return this::setFillColor;
+ }
+
+ throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
+ + propertyIdx);
+ }
+
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
}
}
- private static class VGroup_Delegate implements VNativeObject {
+ static class VGroup_Delegate implements VNativeObject {
// This constants need to be kept in sync with their definitions in VectorDrawable.Group
private static final int ROTATE_INDEX = 0;
private static final int PIVOT_X_INDEX = 1;
private static final int TRANSLATE_X_INDEX = 5;
private static final int TRANSLATE_Y_INDEX = 6;
+ public Consumer<Float> getPropertySetter(int propertyIdx) {
+ switch (propertyIdx) {
+ case ROTATE_INDEX:
+ return this::setRotation;
+ case PIVOT_X_INDEX:
+ return this::setPivotX;
+ case PIVOT_Y_INDEX:
+ return this::setPivotY;
+ case SCALE_X_INDEX:
+ return this::setScaleX;
+ case SCALE_Y_INDEX:
+ return this::setScaleY;
+ case TRANSLATE_X_INDEX:
+ return this::setTranslateX;
+ case TRANSLATE_Y_INDEX:
+ return this::setTranslateY;
+ }
+
+ throw new IllegalArgumentException("Invalid VGroup_Delegate property index "
+ + propertyIdx);
+ }
+
/////////////////////////////////////////////////////
// Variables below need to be copied (deep copy if applicable) for mutation.
final ArrayList<Object> mChildren = new ArrayList<>();
}
}
- private static class VPathRenderer_Delegate implements VNativeObject {
+ static class VPathRenderer_Delegate implements VNativeObject {
/* Right now the internal data structure is organized as a tree.
* Each node can be a group node, or a path.
* A group node can have groups or paths as children, but a path node has
return mRootAlpha;
}
- private void setRootAlpha(float alpha) {
+ void setRootAlpha(float alpha) {
mRootAlpha = alpha;
}
--- /dev/null
+/*
+ * 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.internal.view.animation;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.MathUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnticipateInterpolator;
+import android.view.animation.AnticipateOvershootInterpolator;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.BounceInterpolator;
+import android.view.animation.CycleInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link
+ * NativeInterpolatorFactoryHelper}
+ * <p>
+ * Through the layoutlib_create tool, the original methods of NativeInterpolatorFactoryHelper have
+ * been replaced by calls to methods of the same name in this delegate class.
+ */
+@SuppressWarnings("unused")
+public class NativeInterpolatorFactoryHelper_Delegate {
+ private static final DelegateManager<Interpolator> sManager = new DelegateManager<>
+ (Interpolator.class);
+
+ public static Interpolator getDelegate(long nativePtr) {
+ return sManager.getDelegate(nativePtr);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAccelerateDecelerateInterpolator() {
+ return sManager.addNewDelegate(new AccelerateDecelerateInterpolator());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAccelerateInterpolator(float factor) {
+ return sManager.addNewDelegate(new AccelerateInterpolator(factor));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAnticipateInterpolator(float tension) {
+ return sManager.addNewDelegate(new AnticipateInterpolator(tension));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createAnticipateOvershootInterpolator(float tension) {
+ return sManager.addNewDelegate(new AnticipateOvershootInterpolator(tension));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createBounceInterpolator() {
+ return sManager.addNewDelegate(new BounceInterpolator());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createCycleInterpolator(float cycles) {
+ return sManager.addNewDelegate(new CycleInterpolator(cycles));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createDecelerateInterpolator(float factor) {
+ return sManager.addNewDelegate(new DecelerateInterpolator(factor));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createLinearInterpolator() {
+ return sManager.addNewDelegate(new LinearInterpolator());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createOvershootInterpolator(float tension) {
+ return sManager.addNewDelegate(new OvershootInterpolator(tension));
+ }
+
+ private static class LutInterpolator extends BaseInterpolator {
+ private final float[] mValues;
+ private final int mSize;
+
+ private LutInterpolator(float[] values) {
+ mValues = values;
+ mSize = mValues.length;
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ float lutpos = input * mSize;
+ if (lutpos >= (mSize - 1)) {
+ return mValues[mSize - 1];
+ }
+
+ int ipart = (int) lutpos;
+ float weight = lutpos - ipart;
+
+ int i1 = ipart;
+ int i2 = Math.min(i1 + 1, mSize - 1);
+
+ assert i1 >= 0 && i2 >= 0 : "Negatives in the interpolation";
+
+ return MathUtils.lerp(mValues[i1], mValues[i2], weight);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long createLutInterpolator(float[] values) {
+ return sManager.addNewDelegate(new LutInterpolator(values));
+ }
+}
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.BridgeAssetManager;
-import android.content.res.BridgeResources;
import android.content.res.BridgeTypedArray;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.content.res.Resources_Delegate;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
public void initResources() {
AssetManager assetManager = AssetManager.getSystem();
- mSystemResources = BridgeResources.initSystem(
+ mSystemResources = Resources_Delegate.initSystem(
this,
assetManager,
mMetrics,
* Disposes the {@link Resources} singleton.
*/
public void disposeResources() {
- BridgeResources.disposeSystem();
+ Resources_Delegate.disposeSystem();
}
public void setBridgeInflater(BridgeInflater inflater) {
List<Pair<String, Boolean>> attributeList = searchAttrs(attrs);
- BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
- isPlatformFile);
+ BridgeTypedArray ta =
+ Resources_Delegate.newTypeArray(mSystemResources, attrs.length, isPlatformFile);
// look for a custom style.
String customStyle = null;
List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
- BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
+ BridgeTypedArray ta = Resources_Delegate.newTypeArray(mSystemResources, attrs.length,
false);
// for each attribute, get its name so that we can search it in the style
--- /dev/null
+/*
+ * 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.layoutlib.bridge.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+/**
+ * Simpler wrapper around FileInputStream. This is used when the input stream represent
+ * not a normal bitmap but a nine patch.
+ * This is useful when the InputStream is created in a method but used in another that needs
+ * to know whether this is 9-patch or not, such as BitmapFactory.
+ */
+public class NinePatchInputStream extends FileInputStream {
+ private boolean mFakeMarkSupport = true;
+ public NinePatchInputStream(File file) throws FileNotFoundException {
+ super(file);
+ }
+
+ @Override
+ public boolean markSupported() {
+ if (mFakeMarkSupport) {
+ // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
+ return true;
+ }
+
+ return super.markSupported();
+ }
+
+ public void disableFakeMarkSupport() {
+ // disable fake mark support so that in case codec actually try to use them
+ // we don't lie to them.
+ mFakeMarkSupport = false;
+ }
+}
ClassVisitor cv = cw;
+ // FIXME Generify
+ if ("android/content/res/Resources".equals(className)) {
+ cv = new FieldInjectorAdapter(cv);
+ }
if (mReplaceMethodCallsClasses.contains(className)) {
cv = new ReplaceMethodCallsAdapter(cv, className);
}
}
return buffer.toByteArray();
}
+
}
*/
public final static String[] DELEGATE_METHODS = new String[] {
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
+ "android.content.res.Resources#getAnimation",
+ "android.content.res.Resources#getBoolean",
+ "android.content.res.Resources#getColor",
+ "android.content.res.Resources#getColorStateList",
+ "android.content.res.Resources#getDimension",
+ "android.content.res.Resources#getDimensionPixelOffset",
+ "android.content.res.Resources#getDimensionPixelSize",
+ "android.content.res.Resources#getDrawable",
+ "android.content.res.Resources#getIntArray",
+ "android.content.res.Resources#getInteger",
+ "android.content.res.Resources#getLayout",
+ "android.content.res.Resources#getResourceEntryName",
+ "android.content.res.Resources#getResourceName",
+ "android.content.res.Resources#getResourceTypeName",
+ "android.content.res.Resources#getString",
+ "android.content.res.Resources#getStringArray",
+ "android.content.res.Resources#getText",
+ "android.content.res.Resources#getTextArray",
+ "android.content.res.Resources#getValue",
+ "android.content.res.Resources#getXml",
+ "android.content.res.Resources#loadXmlResourceParser",
+ "android.content.res.Resources#obtainAttributes",
+ "android.content.res.Resources#obtainTypedArray",
+ "android.content.res.Resources#openRawResource",
+ "android.content.res.Resources#openRawResourceFd",
"android.content.res.Resources$Theme#obtainStyledAttributes",
"android.content.res.Resources$Theme#resolveAttribute",
"android.content.res.Resources$Theme#resolveAttributes",
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
"android.graphics.BitmapFactory#setDensityFromOptions",
+ "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget",
"android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.Typeface#getSystemFontConfigLocation",
"android.graphics.Typeface#makeFamilyFromParsed",
"android.graphics.SweepGradient",
"android.graphics.Typeface",
"android.graphics.Xfermode",
+ "android.graphics.drawable.AnimatedVectorDrawable",
"android.graphics.drawable.VectorDrawable",
"android.os.SystemClock",
"android.os.SystemProperties",
"android.text.StaticLayout",
"android.util.PathParser",
"android.view.Display",
+ "com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
"libcore.icu.ICU",
};
// Use android.icu.text versions of DateFormat and SimpleDateFormat since the
// original ones do not match the Android implementation
"java.text.DateFormat", "android.icu.text.DateFormat",
- "java.text.SimpleDateFormat", "android.icu.text.SimpleDateFormat"
+ "java.text.SimpleDateFormat", "android.icu.text.SimpleDateFormat",
};
private final static String[] EXCLUDED_CLASSES =
--- /dev/null
+/*
+ * 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.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Injects fields in a class.
+ * <p>
+ * TODO: Generify
+ */
+public class FieldInjectorAdapter extends ClassVisitor {
+ public FieldInjectorAdapter(ClassVisitor cv) {
+ super(Opcodes.ASM4, cv);
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitField(Opcodes.ACC_PUBLIC, "mLayoutlibCallback",
+ "Lcom/android/ide/common/rendering/api/LayoutlibCallback;", null, null);
+ super.visitField(Opcodes.ACC_PUBLIC, "mContext",
+ "Lcom/android/layoutlib/bridge/android/BridgeContext;", null, null);
+ super.visitEnd();
+ }
+}