1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.text.TextUtils;
24import android.util.proto.ProtoOutputStream;
25
26import java.io.PrintWriter;
27
28/**
29 * Identifier for a specific application component
30 * ({@link android.app.Activity}, {@link android.app.Service},
31 * {@link android.content.BroadcastReceiver}, or
32 * {@link android.content.ContentProvider}) that is available.  Two
33 * pieces of information, encapsulated here, are required to identify
34 * a component: the package (a String) it exists in, and the class (a String)
35 * name inside of that package.
36 *
37 */
38public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
39    private final String mPackage;
40    private final String mClass;
41
42    /**
43     * Create a new component identifier where the class name may be specified
44     * as either absolute or relative to the containing package.
45     *
46     * <p>Relative package names begin with a <code>'.'</code> character. For a package
47     * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
48     * will return a ComponentName with the package <code>"com.example"</code>and class name
49     * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
50     * permitted.</p>
51     *
52     * @param pkg the name of the package the component exists in
53     * @param cls the name of the class inside of <var>pkg</var> that implements
54     *            the component
55     * @return the new ComponentName
56     */
57    public static @NonNull ComponentName createRelative(@NonNull String pkg, @NonNull String cls) {
58        if (TextUtils.isEmpty(cls)) {
59            throw new IllegalArgumentException("class name cannot be empty");
60        }
61
62        final String fullName;
63        if (cls.charAt(0) == '.') {
64            // Relative to the package. Prepend the package name.
65            fullName = pkg + cls;
66        } else {
67            // Fully qualified package name.
68            fullName = cls;
69        }
70        return new ComponentName(pkg, fullName);
71    }
72
73    /**
74     * Create a new component identifier where the class name may be specified
75     * as either absolute or relative to the containing package.
76     *
77     * <p>Relative package names begin with a <code>'.'</code> character. For a package
78     * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
79     * will return a ComponentName with the package <code>"com.example"</code>and class name
80     * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
81     * permitted.</p>
82     *
83     * @param pkg a Context for the package implementing the component
84     * @param cls the name of the class inside of <var>pkg</var> that implements
85     *            the component
86     * @return the new ComponentName
87     */
88    public static @NonNull ComponentName createRelative(@NonNull Context pkg, @NonNull String cls) {
89        return createRelative(pkg.getPackageName(), cls);
90    }
91
92    /**
93     * Create a new component identifier.
94     *
95     * @param pkg The name of the package that the component exists in.  Can
96     * not be null.
97     * @param cls The name of the class inside of <var>pkg</var> that
98     * implements the component.  Can not be null.
99     */
100    public ComponentName(@NonNull String pkg, @NonNull String cls) {
101        if (pkg == null) throw new NullPointerException("package name is null");
102        if (cls == null) throw new NullPointerException("class name is null");
103        mPackage = pkg;
104        mClass = cls;
105    }
106
107    /**
108     * Create a new component identifier from a Context and class name.
109     *
110     * @param pkg A Context for the package implementing the component,
111     * from which the actual package name will be retrieved.
112     * @param cls The name of the class inside of <var>pkg</var> that
113     * implements the component.
114     */
115    public ComponentName(@NonNull Context pkg, @NonNull String cls) {
116        if (cls == null) throw new NullPointerException("class name is null");
117        mPackage = pkg.getPackageName();
118        mClass = cls;
119    }
120
121    /**
122     * Create a new component identifier from a Context and Class object.
123     *
124     * @param pkg A Context for the package implementing the component, from
125     * which the actual package name will be retrieved.
126     * @param cls The Class object of the desired component, from which the
127     * actual class name will be retrieved.
128     */
129    public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) {
130        mPackage = pkg.getPackageName();
131        mClass = cls.getName();
132    }
133
134    public ComponentName clone() {
135        return new ComponentName(mPackage, mClass);
136    }
137
138    /**
139     * Return the package name of this component.
140     */
141    public @NonNull String getPackageName() {
142        return mPackage;
143    }
144
145    /**
146     * Return the class name of this component.
147     */
148    public @NonNull String getClassName() {
149        return mClass;
150    }
151
152    /**
153     * Return the class name, either fully qualified or in a shortened form
154     * (with a leading '.') if it is a suffix of the package.
155     */
156    public String getShortClassName() {
157        if (mClass.startsWith(mPackage)) {
158            int PN = mPackage.length();
159            int CN = mClass.length();
160            if (CN > PN && mClass.charAt(PN) == '.') {
161                return mClass.substring(PN, CN);
162            }
163        }
164        return mClass;
165    }
166
167    private static void appendShortClassName(StringBuilder sb, String packageName,
168            String className) {
169        if (className.startsWith(packageName)) {
170            int PN = packageName.length();
171            int CN = className.length();
172            if (CN > PN && className.charAt(PN) == '.') {
173                sb.append(className, PN, CN);
174                return;
175            }
176        }
177        sb.append(className);
178    }
179
180    private static void printShortClassName(PrintWriter pw, String packageName,
181            String className) {
182        if (className.startsWith(packageName)) {
183            int PN = packageName.length();
184            int CN = className.length();
185            if (CN > PN && className.charAt(PN) == '.') {
186                pw.write(className, PN, CN-PN);
187                return;
188            }
189        }
190        pw.print(className);
191    }
192
193    /**
194     * Return a String that unambiguously describes both the package and
195     * class names contained in the ComponentName.  You can later recover
196     * the ComponentName from this string through
197     * {@link #unflattenFromString(String)}.
198     *
199     * @return Returns a new String holding the package and class names.  This
200     * is represented as the package name, concatenated with a '/' and then the
201     * class name.
202     *
203     * @see #unflattenFromString(String)
204     */
205    public @NonNull String flattenToString() {
206        return mPackage + "/" + mClass;
207    }
208
209    /**
210     * The same as {@link #flattenToString()}, but abbreviates the class
211     * name if it is a suffix of the package.  The result can still be used
212     * with {@link #unflattenFromString(String)}.
213     *
214     * @return Returns a new String holding the package and class names.  This
215     * is represented as the package name, concatenated with a '/' and then the
216     * class name.
217     *
218     * @see #unflattenFromString(String)
219     */
220    public @NonNull String flattenToShortString() {
221        StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length());
222        appendShortString(sb, mPackage, mClass);
223        return sb.toString();
224    }
225
226    /** @hide */
227    public void appendShortString(StringBuilder sb) {
228        appendShortString(sb, mPackage, mClass);
229    }
230
231    /** @hide */
232    public static void appendShortString(StringBuilder sb, String packageName, String className) {
233        sb.append(packageName).append('/');
234        appendShortClassName(sb, packageName, className);
235    }
236
237    /** @hide */
238    public static void printShortString(PrintWriter pw, String packageName, String className) {
239        pw.print(packageName);
240        pw.print('/');
241        printShortClassName(pw, packageName, className);
242    }
243
244    /**
245     * Recover a ComponentName from a String that was previously created with
246     * {@link #flattenToString()}.  It splits the string at the first '/',
247     * taking the part before as the package name and the part after as the
248     * class name.  As a special convenience (to use, for example, when
249     * parsing component names on the command line), if the '/' is immediately
250     * followed by a '.' then the final class name will be the concatenation
251     * of the package name with the string following the '/'.  Thus
252     * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
253     *
254     * @param str The String that was returned by flattenToString().
255     * @return Returns a new ComponentName containing the package and class
256     * names that were encoded in <var>str</var>
257     *
258     * @see #flattenToString()
259     */
260    public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
261        int sep = str.indexOf('/');
262        if (sep < 0 || (sep+1) >= str.length()) {
263            return null;
264        }
265        String pkg = str.substring(0, sep);
266        String cls = str.substring(sep+1);
267        if (cls.length() > 0 && cls.charAt(0) == '.') {
268            cls = pkg + cls;
269        }
270        return new ComponentName(pkg, cls);
271    }
272
273    /**
274     * Return string representation of this class without the class's name
275     * as a prefix.
276     */
277    public String toShortString() {
278        return "{" + mPackage + "/" + mClass + "}";
279    }
280
281    @Override
282    public String toString() {
283        return "ComponentInfo{" + mPackage + "/" + mClass + "}";
284    }
285
286    /** Put this here so that individual services don't have to reimplement this. @hide */
287    public void writeToProto(ProtoOutputStream proto, long fieldId) {
288        final long token = proto.start(fieldId);
289        proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
290        proto.write(ComponentNameProto.CLASS_NAME, mClass);
291        proto.end(token);
292    }
293
294    @Override
295    public boolean equals(Object obj) {
296        try {
297            if (obj != null) {
298                ComponentName other = (ComponentName)obj;
299                // Note: no null checks, because mPackage and mClass can
300                // never be null.
301                return mPackage.equals(other.mPackage)
302                        && mClass.equals(other.mClass);
303            }
304        } catch (ClassCastException e) {
305        }
306        return false;
307    }
308
309    @Override
310    public int hashCode() {
311        return mPackage.hashCode() + mClass.hashCode();
312    }
313
314    public int compareTo(ComponentName that) {
315        int v;
316        v = this.mPackage.compareTo(that.mPackage);
317        if (v != 0) {
318            return v;
319        }
320        return this.mClass.compareTo(that.mClass);
321    }
322
323    public int describeContents() {
324        return 0;
325    }
326
327    public void writeToParcel(Parcel out, int flags) {
328        out.writeString(mPackage);
329        out.writeString(mClass);
330    }
331
332    /**
333     * Write a ComponentName to a Parcel, handling null pointers.  Must be
334     * read with {@link #readFromParcel(Parcel)}.
335     *
336     * @param c The ComponentName to be written.
337     * @param out The Parcel in which the ComponentName will be placed.
338     *
339     * @see #readFromParcel(Parcel)
340     */
341    public static void writeToParcel(ComponentName c, Parcel out) {
342        if (c != null) {
343            c.writeToParcel(out, 0);
344        } else {
345            out.writeString(null);
346        }
347    }
348
349    /**
350     * Read a ComponentName from a Parcel that was previously written
351     * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
352     * a null or new object as appropriate.
353     *
354     * @param in The Parcel from which to read the ComponentName
355     * @return Returns a new ComponentName matching the previously written
356     * object, or null if a null had been written.
357     *
358     * @see #writeToParcel(ComponentName, Parcel)
359     */
360    public static ComponentName readFromParcel(Parcel in) {
361        String pkg = in.readString();
362        return pkg != null ? new ComponentName(pkg, in) : null;
363    }
364
365    public static final Parcelable.Creator<ComponentName> CREATOR
366            = new Parcelable.Creator<ComponentName>() {
367        public ComponentName createFromParcel(Parcel in) {
368            return new ComponentName(in);
369        }
370
371        public ComponentName[] newArray(int size) {
372            return new ComponentName[size];
373        }
374    };
375
376    /**
377     * Instantiate a new ComponentName from the data in a Parcel that was
378     * previously written with {@link #writeToParcel(Parcel, int)}.  Note that you
379     * must not use this with data written by
380     * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
381     * to handle a null ComponentObject here.
382     *
383     * @param in The Parcel containing the previously written ComponentName,
384     * positioned at the location in the buffer where it was written.
385     */
386    public ComponentName(Parcel in) {
387        mPackage = in.readString();
388        if (mPackage == null) throw new NullPointerException(
389                "package name is null");
390        mClass = in.readString();
391        if (mClass == null) throw new NullPointerException(
392                "class name is null");
393    }
394
395    private ComponentName(String pkg, Parcel in) {
396        mPackage = pkg;
397        mClass = in.readString();
398    }
399
400    /**
401     * Interface for classes associated with a component name.
402     * @hide
403     */
404    @FunctionalInterface
405    public interface WithComponentName {
406        /** Return the associated component name. */
407        ComponentName getComponentName();
408    }
409}
410